From 7853b3f17d3b57d7ac91bc909a57143674f57272 Mon Sep 17 00:00:00 2001 From: feld Date: Fri, 30 Aug 2019 00:38:03 +0000 Subject: [PATCH 01/14] Fix AntiFollowbotPolicy when trying to follow a relay --- CHANGELOG.md | 1 + .../web/activity_pub/mrf/anti_followbot_policy.ex | 12 ++++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 20af9badc..4acb749ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Reverse Proxy limiting `max_body_length` was incorrectly defined and only checked `Content-Length` headers which may not be sufficient in some circumstances - MRF: fix use of unserializable keyword lists in describe() implementations - ActivityPub: Deactivated user deletion +- MRF: fix ability to follow a relay when AntiFollowbotPolicy was enabled ### Added - Expiring/ephemeral activites. All activities can have expires_at value set, which controls when they should be deleted automatically. diff --git a/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex b/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex index de1eb4aa5..b3547ecd4 100644 --- a/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex @@ -25,11 +25,15 @@ defp score_displayname("fedibot"), do: 1.0 defp score_displayname(_), do: 0.0 defp determine_if_followbot(%User{nickname: nickname, name: displayname}) do - # nickname will always be a binary string because it's generated by Pleroma. + # nickname will be a binary string except when following a relay nick_score = - nickname - |> String.downcase() - |> score_nickname() + if is_binary(nickname) do + nickname + |> String.downcase() + |> score_nickname() + else + 0.0 + end # displayname will either be a binary string or nil, if a displayname isn't set. name_score = From 99b4847da3244a0d023ae25b2669afb07a4eda4f Mon Sep 17 00:00:00 2001 From: kPherox Date: Fri, 30 Aug 2019 21:00:50 +0900 Subject: [PATCH 02/14] Fix missing changes in pleroma/pleroma!1197 --- installation/pleroma.nginx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/installation/pleroma.nginx b/installation/pleroma.nginx index e3c70de54..4da9918ca 100644 --- a/installation/pleroma.nginx +++ b/installation/pleroma.nginx @@ -71,26 +71,26 @@ server { proxy_set_header Connection "upgrade"; proxy_set_header Host $http_host; - # this is explicitly IPv4 since Pleroma.Web.Endpoint binds on IPv4 only - # and `localhost.` resolves to [::0] on some systems: see issue #930 + # this is explicitly IPv4 since Pleroma.Web.Endpoint binds on IPv4 only + # and `localhost.` resolves to [::0] on some systems: see issue #930 proxy_pass http://127.0.0.1:4000; client_max_body_size 16m; } location ~ ^/(media|proxy) { - proxy_cache pleroma_media_cache; + proxy_cache pleroma_media_cache; slice 1m; proxy_cache_key $host$uri$is_args$args$slice_range; proxy_set_header Range $slice_range; proxy_http_version 1.1; proxy_cache_valid 200 206 301 304 1h; - proxy_cache_lock on; + proxy_cache_lock on; proxy_ignore_client_abort on; - proxy_buffering on; + proxy_buffering on; chunked_transfer_encoding on; proxy_ignore_headers Cache-Control; - proxy_hide_header Cache-Control; - proxy_pass http://localhost:4000; + proxy_hide_header Cache-Control; + proxy_pass http://127.0.0.1:4000; } } From a4c5f71e933c905433b80c90bcd626e7da703669 Mon Sep 17 00:00:00 2001 From: Maxim Filippov Date: Mon, 2 Sep 2019 22:48:52 +0300 Subject: [PATCH 03/14] Return total from pagination + tests --- CHANGELOG.md | 1 + lib/pleroma/activity/search.ex | 1 + lib/pleroma/conversation/participation.ex | 1 + lib/pleroma/notification.ex | 1 + lib/pleroma/pagination.ex | 24 ++++-- lib/pleroma/user/search.ex | 1 + lib/pleroma/web/activity_pub/activity_pub.ex | 3 + .../controllers/mastodon_api_controller.ex | 2 + lib/pleroma/web/mastodon_api/mastodon_api.ex | 4 + test/pagination_test.exs | 78 +++++++++++++++++++ 10 files changed, 109 insertions(+), 7 deletions(-) create mode 100644 test/pagination_test.exs diff --git a/CHANGELOG.md b/CHANGELOG.md index 4acb749ac..06ad303de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Mastodon API: Unsubscribe followers when they unfollow a user - AdminAPI: Add "godmode" while fetching user statuses (i.e. admin can see private statuses) - Improve digest email template +– Pagination: return `total` alongside with `items` when paginating ### Fixed - Following from Osada diff --git a/lib/pleroma/activity/search.ex b/lib/pleroma/activity/search.ex index f847ac238..f7156c81c 100644 --- a/lib/pleroma/activity/search.ex +++ b/lib/pleroma/activity/search.ex @@ -27,6 +27,7 @@ def search(user, search_query, options \\ []) do |> maybe_restrict_local(user) |> maybe_restrict_author(author) |> Pagination.fetch_paginated(%{"offset" => offset, "limit" => limit}, :offset) + |> Map.get(:items) |> maybe_fetch(user, search_query) end diff --git a/lib/pleroma/conversation/participation.ex b/lib/pleroma/conversation/participation.ex index ea5b9fe17..5fd8d3d41 100644 --- a/lib/pleroma/conversation/participation.ex +++ b/lib/pleroma/conversation/participation.ex @@ -67,6 +67,7 @@ def for_user(user, params \\ %{}) do preload: [conversation: [:users]] ) |> Pleroma.Pagination.fetch_paginated(params) + |> Map.get(:items) end def for_user_and_conversation(user, conversation) do diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index 5d29af853..3e4ddd2ba 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -75,6 +75,7 @@ def for_user(user, opts \\ %{}) do user |> for_user_query(opts) |> Pagination.fetch_paginated(opts) + |> Map.get(:items) end @doc """ diff --git a/lib/pleroma/pagination.ex b/lib/pleroma/pagination.ex index 2b869ccdc..d21ecf628 100644 --- a/lib/pleroma/pagination.ex +++ b/lib/pleroma/pagination.ex @@ -18,19 +18,29 @@ def fetch_paginated(query, params, type \\ :keyset) def fetch_paginated(query, params, :keyset) do options = cast_params(params) + total = Repo.aggregate(query, :count, :id) - query - |> paginate(options, :keyset) - |> Repo.all() - |> enforce_order(options) + %{ + total: total, + items: + query + |> paginate(options, :keyset) + |> Repo.all() + |> enforce_order(options) + } end def fetch_paginated(query, params, :offset) do options = cast_params(params) + total = Repo.aggregate(query, :count, :id) - query - |> paginate(options, :offset) - |> Repo.all() + %{ + total: total, + items: + query + |> paginate(options, :offset) + |> Repo.all() + } end def paginate(query, options, method \\ :keyset) diff --git a/lib/pleroma/user/search.ex b/lib/pleroma/user/search.ex index 6fb2c2352..bc05639b5 100644 --- a/lib/pleroma/user/search.ex +++ b/lib/pleroma/user/search.ex @@ -34,6 +34,7 @@ def search(query_string, opts \\ []) do query_string |> search_query(for_user, following) |> Pagination.fetch_paginated(%{"offset" => offset, "limit" => result_limit}, :offset) + |> Map.get(:items) end) results diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index eeb826814..8f07638cd 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -556,6 +556,7 @@ def fetch_public_activities(opts \\ %{}) do q |> restrict_unlisted() |> Pagination.fetch_paginated(opts) + |> Map.get(:items) |> Enum.reverse() end @@ -953,6 +954,7 @@ def fetch_activities(recipients, opts \\ %{}) do fetch_activities_query(recipients ++ list_memberships, opts) |> Pagination.fetch_paginated(opts) + |> Map.get(:items) |> Enum.reverse() |> maybe_update_cc(list_memberships, opts["user"]) end @@ -987,6 +989,7 @@ def fetch_activities_bounded(recipients, recipients_with_public, opts \\ %{}) do fetch_activities_query([], opts) |> fetch_activities_bounded_query(recipients, recipients_with_public) |> Pagination.fetch_paginated(opts) + |> Map.get(:items) |> Enum.reverse() end diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex index 83e877c0e..d532ba685 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -420,6 +420,7 @@ def dm_timeline(%{assigns: %{user: user}} = conn, params) do [user.ap_id] |> ActivityPub.fetch_activities_query(params) |> Pagination.fetch_paginated(params) + |> Map.get(:items) conn |> add_link_headers(:dm_timeline, activities) @@ -1194,6 +1195,7 @@ def bookmarks(%{assigns: %{user: user}} = conn, params) do bookmarks = Bookmark.for_user_query(user.id) |> Pagination.fetch_paginated(params) + |> Map.get(:items) activities = bookmarks diff --git a/lib/pleroma/web/mastodon_api/mastodon_api.ex b/lib/pleroma/web/mastodon_api/mastodon_api.ex index ac01d1ff3..cf3962944 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api.ex @@ -45,12 +45,14 @@ def get_followers(user, params \\ %{}) do user |> User.get_followers_query() |> Pagination.fetch_paginated(params) + |> Map.get(:items) end def get_friends(user, params \\ %{}) do user |> User.get_friends_query() |> Pagination.fetch_paginated(params) + |> Map.get(:items) end def get_notifications(user, params \\ %{}) do @@ -60,12 +62,14 @@ def get_notifications(user, params \\ %{}) do |> Notification.for_user_query(options) |> restrict(:exclude_types, options) |> Pagination.fetch_paginated(params) + |> Map.get(:items) end def get_scheduled_activities(user, params \\ %{}) do user |> ScheduledActivity.for_user_query() |> Pagination.fetch_paginated(params) + |> Map.get(:items) end defp cast_params(params) do diff --git a/test/pagination_test.exs b/test/pagination_test.exs new file mode 100644 index 000000000..048ab6f3c --- /dev/null +++ b/test/pagination_test.exs @@ -0,0 +1,78 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.PaginationTest do + use Pleroma.DataCase + + import Pleroma.Factory + + alias Pleroma.Object + alias Pleroma.Pagination + + describe "keyset" do + setup do + notes = insert_list(5, :note) + + %{notes: notes} + end + + test "paginates by min_id", %{notes: notes} do + id = Enum.at(notes, 2).id |> Integer.to_string() + %{total: total, items: paginated} = Pagination.fetch_paginated(Object, %{"min_id" => id}) + + assert length(paginated) == 2 + assert total == 5 + end + + test "paginates by since_id", %{notes: notes} do + id = Enum.at(notes, 2).id |> Integer.to_string() + %{total: total, items: paginated} = Pagination.fetch_paginated(Object, %{"since_id" => id}) + + assert length(paginated) == 2 + assert total == 5 + end + + test "paginates by max_id", %{notes: notes} do + id = Enum.at(notes, 1).id |> Integer.to_string() + %{total: total, items: paginated} = Pagination.fetch_paginated(Object, %{"max_id" => id}) + + assert length(paginated) == 1 + assert total == 5 + end + + test "paginates by min_id & limit", %{notes: notes} do + id = Enum.at(notes, 2).id |> Integer.to_string() + + %{total: total, items: paginated} = + Pagination.fetch_paginated(Object, %{"min_id" => id, "limit" => 1}) + + assert length(paginated) == 1 + assert total == 5 + end + end + + describe "offset" do + setup do + notes = insert_list(5, :note) + + %{notes: notes} + end + + test "paginates by limit" do + %{total: total, items: paginated} = + Pagination.fetch_paginated(Object, %{"limit" => 2}, :offset) + + assert length(paginated) == 2 + assert total == 5 + end + + test "paginates by limit & offset" do + %{total: total, items: paginated} = + Pagination.fetch_paginated(Object, %{"limit" => 2, "offset" => 4}, :offset) + + assert length(paginated) == 1 + assert total == 5 + end + end +end From b15cfd80ef5d5bc971f78a53dfa3d37dec4499a5 Mon Sep 17 00:00:00 2001 From: Maxim Filippov Date: Tue, 3 Sep 2019 13:58:27 +0300 Subject: [PATCH 04/14] Return "total" optionally --- CHANGELOG.md | 2 +- lib/pleroma/activity/search.ex | 1 - lib/pleroma/conversation/participation.ex | 1 - lib/pleroma/notification.ex | 1 - lib/pleroma/pagination.ex | 38 +++++++++++-------- lib/pleroma/user/search.ex | 1 - lib/pleroma/web/activity_pub/activity_pub.ex | 3 -- .../controllers/mastodon_api_controller.ex | 2 - lib/pleroma/web/mastodon_api/mastodon_api.ex | 4 -- test/pagination_test.exs | 24 ++++++------ 10 files changed, 36 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06ad303de..8264688d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Mastodon API: Unsubscribe followers when they unfollow a user - AdminAPI: Add "godmode" while fetching user statuses (i.e. admin can see private statuses) - Improve digest email template -– Pagination: return `total` alongside with `items` when paginating +– Pagination: (optional) return `total` alongside with `items` when paginating ### Fixed - Following from Osada diff --git a/lib/pleroma/activity/search.ex b/lib/pleroma/activity/search.ex index f7156c81c..f847ac238 100644 --- a/lib/pleroma/activity/search.ex +++ b/lib/pleroma/activity/search.ex @@ -27,7 +27,6 @@ def search(user, search_query, options \\ []) do |> maybe_restrict_local(user) |> maybe_restrict_author(author) |> Pagination.fetch_paginated(%{"offset" => offset, "limit" => limit}, :offset) - |> Map.get(:items) |> maybe_fetch(user, search_query) end diff --git a/lib/pleroma/conversation/participation.ex b/lib/pleroma/conversation/participation.ex index 5fd8d3d41..ea5b9fe17 100644 --- a/lib/pleroma/conversation/participation.ex +++ b/lib/pleroma/conversation/participation.ex @@ -67,7 +67,6 @@ def for_user(user, params \\ %{}) do preload: [conversation: [:users]] ) |> Pleroma.Pagination.fetch_paginated(params) - |> Map.get(:items) end def for_user_and_conversation(user, conversation) do diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index 3e4ddd2ba..5d29af853 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -75,7 +75,6 @@ def for_user(user, opts \\ %{}) do user |> for_user_query(opts) |> Pagination.fetch_paginated(opts) - |> Map.get(:items) end @doc """ diff --git a/lib/pleroma/pagination.ex b/lib/pleroma/pagination.ex index d21ecf628..b55379c4a 100644 --- a/lib/pleroma/pagination.ex +++ b/lib/pleroma/pagination.ex @@ -16,31 +16,39 @@ defmodule Pleroma.Pagination do def fetch_paginated(query, params, type \\ :keyset) - def fetch_paginated(query, params, :keyset) do - options = cast_params(params) + def fetch_paginated(query, %{"total" => true} = params, :keyset) do total = Repo.aggregate(query, :count, :id) %{ total: total, - items: - query - |> paginate(options, :keyset) - |> Repo.all() - |> enforce_order(options) + items: fetch_paginated(query, Map.drop(params, ["total"]), :keyset) + } + end + + def fetch_paginated(query, params, :keyset) do + options = cast_params(params) + + query + |> paginate(options, :keyset) + |> Repo.all() + |> enforce_order(options) + end + + def fetch_paginated(query, %{"total" => true} = params, :offset) do + total = Repo.aggregate(query, :count, :id) + + %{ + total: total, + items: fetch_paginated(query, Map.drop(params, ["total"]), :offset) } end def fetch_paginated(query, params, :offset) do options = cast_params(params) - total = Repo.aggregate(query, :count, :id) - %{ - total: total, - items: - query - |> paginate(options, :offset) - |> Repo.all() - } + query + |> paginate(options, :offset) + |> Repo.all() end def paginate(query, options, method \\ :keyset) diff --git a/lib/pleroma/user/search.ex b/lib/pleroma/user/search.ex index bc05639b5..6fb2c2352 100644 --- a/lib/pleroma/user/search.ex +++ b/lib/pleroma/user/search.ex @@ -34,7 +34,6 @@ def search(query_string, opts \\ []) do query_string |> search_query(for_user, following) |> Pagination.fetch_paginated(%{"offset" => offset, "limit" => result_limit}, :offset) - |> Map.get(:items) end) results diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 8f07638cd..eeb826814 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -556,7 +556,6 @@ def fetch_public_activities(opts \\ %{}) do q |> restrict_unlisted() |> Pagination.fetch_paginated(opts) - |> Map.get(:items) |> Enum.reverse() end @@ -954,7 +953,6 @@ def fetch_activities(recipients, opts \\ %{}) do fetch_activities_query(recipients ++ list_memberships, opts) |> Pagination.fetch_paginated(opts) - |> Map.get(:items) |> Enum.reverse() |> maybe_update_cc(list_memberships, opts["user"]) end @@ -989,7 +987,6 @@ def fetch_activities_bounded(recipients, recipients_with_public, opts \\ %{}) do fetch_activities_query([], opts) |> fetch_activities_bounded_query(recipients, recipients_with_public) |> Pagination.fetch_paginated(opts) - |> Map.get(:items) |> Enum.reverse() end diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex index d532ba685..83e877c0e 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -420,7 +420,6 @@ def dm_timeline(%{assigns: %{user: user}} = conn, params) do [user.ap_id] |> ActivityPub.fetch_activities_query(params) |> Pagination.fetch_paginated(params) - |> Map.get(:items) conn |> add_link_headers(:dm_timeline, activities) @@ -1195,7 +1194,6 @@ def bookmarks(%{assigns: %{user: user}} = conn, params) do bookmarks = Bookmark.for_user_query(user.id) |> Pagination.fetch_paginated(params) - |> Map.get(:items) activities = bookmarks diff --git a/lib/pleroma/web/mastodon_api/mastodon_api.ex b/lib/pleroma/web/mastodon_api/mastodon_api.ex index cf3962944..ac01d1ff3 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api.ex @@ -45,14 +45,12 @@ def get_followers(user, params \\ %{}) do user |> User.get_followers_query() |> Pagination.fetch_paginated(params) - |> Map.get(:items) end def get_friends(user, params \\ %{}) do user |> User.get_friends_query() |> Pagination.fetch_paginated(params) - |> Map.get(:items) end def get_notifications(user, params \\ %{}) do @@ -62,14 +60,12 @@ def get_notifications(user, params \\ %{}) do |> Notification.for_user_query(options) |> restrict(:exclude_types, options) |> Pagination.fetch_paginated(params) - |> Map.get(:items) end def get_scheduled_activities(user, params \\ %{}) do user |> ScheduledActivity.for_user_query() |> Pagination.fetch_paginated(params) - |> Map.get(:items) end defp cast_params(params) do diff --git a/test/pagination_test.exs b/test/pagination_test.exs index 048ab6f3c..c0fbe7933 100644 --- a/test/pagination_test.exs +++ b/test/pagination_test.exs @@ -19,7 +19,9 @@ defmodule Pleroma.PaginationTest do test "paginates by min_id", %{notes: notes} do id = Enum.at(notes, 2).id |> Integer.to_string() - %{total: total, items: paginated} = Pagination.fetch_paginated(Object, %{"min_id" => id}) + + %{total: total, items: paginated} = + Pagination.fetch_paginated(Object, %{"min_id" => id, "total" => true}) assert length(paginated) == 2 assert total == 5 @@ -27,7 +29,9 @@ test "paginates by min_id", %{notes: notes} do test "paginates by since_id", %{notes: notes} do id = Enum.at(notes, 2).id |> Integer.to_string() - %{total: total, items: paginated} = Pagination.fetch_paginated(Object, %{"since_id" => id}) + + %{total: total, items: paginated} = + Pagination.fetch_paginated(Object, %{"since_id" => id, "total" => true}) assert length(paginated) == 2 assert total == 5 @@ -35,7 +39,9 @@ test "paginates by since_id", %{notes: notes} do test "paginates by max_id", %{notes: notes} do id = Enum.at(notes, 1).id |> Integer.to_string() - %{total: total, items: paginated} = Pagination.fetch_paginated(Object, %{"max_id" => id}) + + %{total: total, items: paginated} = + Pagination.fetch_paginated(Object, %{"max_id" => id, "total" => true}) assert length(paginated) == 1 assert total == 5 @@ -44,11 +50,9 @@ test "paginates by max_id", %{notes: notes} do test "paginates by min_id & limit", %{notes: notes} do id = Enum.at(notes, 2).id |> Integer.to_string() - %{total: total, items: paginated} = - Pagination.fetch_paginated(Object, %{"min_id" => id, "limit" => 1}) + paginated = Pagination.fetch_paginated(Object, %{"min_id" => id, "limit" => 1}) assert length(paginated) == 1 - assert total == 5 end end @@ -60,19 +64,15 @@ test "paginates by min_id & limit", %{notes: notes} do end test "paginates by limit" do - %{total: total, items: paginated} = - Pagination.fetch_paginated(Object, %{"limit" => 2}, :offset) + paginated = Pagination.fetch_paginated(Object, %{"limit" => 2}, :offset) assert length(paginated) == 2 - assert total == 5 end test "paginates by limit & offset" do - %{total: total, items: paginated} = - Pagination.fetch_paginated(Object, %{"limit" => 2, "offset" => 4}, :offset) + paginated = Pagination.fetch_paginated(Object, %{"limit" => 2, "offset" => 4}, :offset) assert length(paginated) == 1 - assert total == 5 end end end From bd8b92ea5e1bb6a97b02e2335fbcaf389ded2c1e Mon Sep 17 00:00:00 2001 From: Ashlynn Anderson Date: Mon, 5 Aug 2019 15:35:34 -0400 Subject: [PATCH 05/14] Remove dynamic config as default, add healthcheck --- config/docker.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/docker.exs b/config/docker.exs index 63ab4cdee..f9f27d141 100644 --- a/config/docker.exs +++ b/config/docker.exs @@ -10,7 +10,7 @@ notify_email: System.get_env("NOTIFY_EMAIL"), limit: 5000, registrations_open: false, - dynamic_configuration: true + healthcheck: true config :pleroma, Pleroma.Repo, adapter: Ecto.Adapters.Postgres, From 4b422b54699ac55a1bc32d2b42c0d55d0b68b4fb Mon Sep 17 00:00:00 2001 From: Ashlynn Anderson Date: Tue, 3 Sep 2019 11:44:57 -0400 Subject: [PATCH 06/14] Switch to official elixir:1.9-alpine image for build --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 268ec61dc..59a352bbc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM rinpatch/elixir:1.9.0-rc.0-alpine as build +FROM elixir:1.9-alpine as build COPY . . From c2b6c1b089a813cf8c7cbd54c0f80bee4985522c Mon Sep 17 00:00:00 2001 From: rinpatch Date: Wed, 4 Sep 2019 11:33:08 +0300 Subject: [PATCH 07/14] Extend `/api/pleroma/notifications/read` to mark multiple notifications as read and make it respond with Mastoapi entities --- CHANGELOG.md | 1 + docs/api/pleroma_api.md | 11 ++-- lib/pleroma/notification.ex | 21 ++++++- .../web/pleroma_api/pleroma_api_controller.ex | 25 +++++++++ lib/pleroma/web/router.ex | 7 +-- .../pleroma_api_controller_test.exs | 56 +++++++++++++++++++ test/web/twitter_api/util_controller_test.exs | 32 ----------- 7 files changed, 108 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8264688d6..40f4580f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Changed - **Breaking:** Configuration: A setting to explicitly disable the mailer was added, defaulting to true, if you are using a mailer add `config :pleroma, Pleroma.Emails.Mailer, enabled: true` to your config - **Breaking:** Configuration: `/media/` is now removed when `base_url` is configured, append `/media/` to your `base_url` config to keep the old behaviour if desired +- **Breaking:** `/api/pleroma/notifications/read` is moved to `/api/v1/pleroma/notifications/read` and now supports `max_id` and responds with Mastodon API entities. - Configuration: OpenGraph and TwitterCard providers enabled by default - Configuration: Filter.AnonymizeFilename added ability to retain file extension with custom text - Federation: Return 403 errors when trying to request pages from a user's follower/following collections if they have `hide_followers`/`hide_follows` set diff --git a/docs/api/pleroma_api.md b/docs/api/pleroma_api.md index b134b31a8..e76a35b3b 100644 --- a/docs/api/pleroma_api.md +++ b/docs/api/pleroma_api.md @@ -126,13 +126,14 @@ Request parameters can be passed via [query strings](https://en.wikipedia.org/wi ## `/api/pleroma/admin/`… See [Admin-API](Admin-API.md) -## `/api/pleroma/notifications/read` -### Mark a single notification as read +## `/api/pleroma/v1/notifications/read` +### Mark notifications as read * Method `POST` * Authentication: required -* Params: - * `id`: notification's id -* Response: JSON. Returns `{"status": "success"}` if the reading was successful, otherwise returns `{"error": "error_msg"}` +* Params (mutually exclusive): + * `id`: a single notification id to read + * `max_id`: read all notifications up to this id +* Response: Notification entity/Array of Notification entities. In case of `max_id`, only the first 80 notifications will be returned. ## `/api/v1/pleroma/accounts/:id/subscribe` ### Subscribe to receive notifications for all statuses posted by a user diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index 5d29af853..d7e232992 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -102,15 +102,32 @@ def set_read_up_to(%{id: user_id} = _user, id) do n in Notification, where: n.user_id == ^user_id, where: n.id <= ^id, + where: n.seen == false, update: [ set: [ seen: true, updated_at: ^NaiveDateTime.utc_now() ] - ] + ], + # Ideally we would preload object and activities here + # but Ecto does not support preloads in update_all + select: n.id ) - Repo.update_all(query, []) + {_, notification_ids} = Repo.update_all(query, []) + + from(n in Notification, where: n.id in ^notification_ids) + |> join(:inner, [n], activity in assoc(n, :activity)) + |> join(:left, [n, a], object in Object, + on: + fragment( + "(?->>'id') = COALESCE((? -> 'object'::text) ->> 'id'::text)", + object.data, + a.data + ) + ) + |> preload([n, a, o], activity: {a, object: o}) + |> Repo.all() end def read_one(%User{} = user, notification_id) do diff --git a/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex b/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex index b6d2bf86b..f4df3b024 100644 --- a/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex @@ -8,8 +8,10 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do import Pleroma.Web.ControllerHelper, only: [add_link_headers: 7] alias Pleroma.Conversation.Participation + alias Pleroma.Notification alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.MastodonAPI.ConversationView + alias Pleroma.Web.MastodonAPI.NotificationView alias Pleroma.Web.MastodonAPI.StatusView def conversation(%{assigns: %{user: user}} = conn, %{"id" => participation_id}) do @@ -70,4 +72,27 @@ def update_conversation( |> render("participation.json", %{participation: participation, for: user}) end end + + def read_notification(%{assigns: %{user: user}} = conn, %{"id" => notification_id}) do + with {:ok, notification} <- Notification.read_one(user, notification_id) do + conn + |> put_view(NotificationView) + |> render("show.json", %{notification: notification, for: user}) + else + {:error, message} -> + conn + |> put_status(:bad_request) + |> json(%{"error" => message}) + end + end + + def read_notification(%{assigns: %{user: user}} = conn, %{"max_id" => max_id}) do + with notifications <- Notification.set_read_up_to(user, max_id) do + notifications = Enum.take(notifications, 80) + + conn + |> put_view(NotificationView) + |> render("index.json", %{notifications: notifications, for: user}) + end + end end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 969dc66fd..44a4279f7 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -236,12 +236,6 @@ defmodule Pleroma.Web.Router do post("/blocks_import", UtilController, :blocks_import) post("/follow_import", UtilController, :follow_import) end - - scope [] do - pipe_through(:oauth_read) - - post("/notifications/read", UtilController, :notifications_read) - end end scope "/oauth", Pleroma.Web.OAuth do @@ -277,6 +271,7 @@ defmodule Pleroma.Web.Router do scope [] do pipe_through(:oauth_write) patch("/conversations/:id", PleromaAPIController, :update_conversation) + post("/notifications/read", PleromaAPIController, :read_notification) end end diff --git a/test/web/pleroma_api/pleroma_api_controller_test.exs b/test/web/pleroma_api/pleroma_api_controller_test.exs index ed6b79727..7eaeda4a0 100644 --- a/test/web/pleroma_api/pleroma_api_controller_test.exs +++ b/test/web/pleroma_api/pleroma_api_controller_test.exs @@ -6,6 +6,7 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIControllerTest do use Pleroma.Web.ConnCase alias Pleroma.Conversation.Participation + alias Pleroma.Notification alias Pleroma.Repo alias Pleroma.Web.CommonAPI @@ -91,4 +92,59 @@ test "PATCH /api/v1/pleroma/conversations/:id", %{conn: conn} do assert user in participation.recipients assert other_user in participation.recipients end + + describe "POST /api/v1/pleroma/notifications/read" do + test "it marks a single notification as read", %{conn: conn} do + user1 = insert(:user) + user2 = insert(:user) + {:ok, activity1} = CommonAPI.post(user2, %{"status" => "hi @#{user1.nickname}"}) + {:ok, activity2} = CommonAPI.post(user2, %{"status" => "hi @#{user1.nickname}"}) + {:ok, [notification1]} = Notification.create_notifications(activity1) + {:ok, [notification2]} = Notification.create_notifications(activity2) + + response = + conn + |> assign(:user, user1) + |> post("/api/v1/pleroma/notifications/read", %{"id" => "#{notification1.id}"}) + |> json_response(:ok) + + assert %{"pleroma" => %{"is_seen" => true}} = response + assert Repo.get(Notification, notification1.id).seen + refute Repo.get(Notification, notification2.id).seen + end + + test "it marks multiple notifications as read", %{conn: conn} do + user1 = insert(:user) + user2 = insert(:user) + {:ok, _activity1} = CommonAPI.post(user2, %{"status" => "hi @#{user1.nickname}"}) + {:ok, _activity2} = CommonAPI.post(user2, %{"status" => "hi @#{user1.nickname}"}) + {:ok, _activity3} = CommonAPI.post(user2, %{"status" => "HIE @#{user1.nickname}"}) + + [notification3, notification2, notification1] = Notification.for_user(user1, %{limit: 3}) + + [response1, response2] = + conn + |> assign(:user, user1) + |> post("/api/v1/pleroma/notifications/read", %{"max_id" => "#{notification2.id}"}) + |> json_response(:ok) + + assert %{"pleroma" => %{"is_seen" => true}} = response1 + assert %{"pleroma" => %{"is_seen" => true}} = response2 + assert Repo.get(Notification, notification1.id).seen + assert Repo.get(Notification, notification2.id).seen + refute Repo.get(Notification, notification3.id).seen + end + + test "it returns error when notification not found", %{conn: conn} do + user1 = insert(:user) + + response = + conn + |> assign(:user, user1) + |> post("/api/v1/pleroma/notifications/read", %{"id" => "22222222222222"}) + |> json_response(:bad_request) + + assert response == %{"error" => "Cannot get notification"} + end + end end diff --git a/test/web/twitter_api/util_controller_test.exs b/test/web/twitter_api/util_controller_test.exs index fe4ffdb59..cf8e69d2b 100644 --- a/test/web/twitter_api/util_controller_test.exs +++ b/test/web/twitter_api/util_controller_test.exs @@ -5,7 +5,6 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do use Pleroma.Web.ConnCase - alias Pleroma.Notification alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web.CommonAPI @@ -141,37 +140,6 @@ test "it imports blocks users from file", %{conn: conn} do end end - describe "POST /api/pleroma/notifications/read" do - test "it marks a single notification as read", %{conn: conn} do - user1 = insert(:user) - user2 = insert(:user) - {:ok, activity1} = CommonAPI.post(user2, %{"status" => "hi @#{user1.nickname}"}) - {:ok, activity2} = CommonAPI.post(user2, %{"status" => "hi @#{user1.nickname}"}) - {:ok, [notification1]} = Notification.create_notifications(activity1) - {:ok, [notification2]} = Notification.create_notifications(activity2) - - conn - |> assign(:user, user1) - |> post("/api/pleroma/notifications/read", %{"id" => "#{notification1.id}"}) - |> json_response(:ok) - - assert Repo.get(Notification, notification1.id).seen - refute Repo.get(Notification, notification2.id).seen - end - - test "it returns error when notification not found", %{conn: conn} do - user1 = insert(:user) - - response = - conn - |> assign(:user, user1) - |> post("/api/pleroma/notifications/read", %{"id" => "22222222222222"}) - |> json_response(403) - - assert response == %{"error" => "Cannot get notification"} - end - end - describe "PUT /api/pleroma/notification_settings" do test "it updates notification settings", %{conn: conn} do user = insert(:user) From 7c3838090f86fbfdbf4e45fcfbabc21c19f26924 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Wed, 4 Sep 2019 10:14:15 +0000 Subject: [PATCH 08/14] Apply suggestion to lib/pleroma/notification.ex --- lib/pleroma/notification.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index d7e232992..b7c880c51 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -116,7 +116,8 @@ def set_read_up_to(%{id: user_id} = _user, id) do {_, notification_ids} = Repo.update_all(query, []) - from(n in Notification, where: n.id in ^notification_ids) + Notification + |> where([n], n.id in ^notification_ids) |> join(:inner, [n], activity in assoc(n, :activity)) |> join(:left, [n, a], object in Object, on: From 377aa9fb90ff1c8537112f23bfc329f1ac0696b4 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Wed, 4 Sep 2019 10:37:43 +0000 Subject: [PATCH 09/14] Apply suggestion to docs/api/pleroma_api.md --- docs/api/pleroma_api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/pleroma_api.md b/docs/api/pleroma_api.md index e76a35b3b..c08ee9ecd 100644 --- a/docs/api/pleroma_api.md +++ b/docs/api/pleroma_api.md @@ -126,7 +126,7 @@ Request parameters can be passed via [query strings](https://en.wikipedia.org/wi ## `/api/pleroma/admin/`… See [Admin-API](Admin-API.md) -## `/api/pleroma/v1/notifications/read` +## `/api/v1/pleroma/notifications/read` ### Mark notifications as read * Method `POST` * Authentication: required From 328b2612cd957aa3ad810101a20037e4e9843bb0 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Wed, 4 Sep 2019 13:39:39 +0300 Subject: [PATCH 10/14] Clarify that read notifications are returned --- docs/api/pleroma_api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/pleroma_api.md b/docs/api/pleroma_api.md index c08ee9ecd..7d343e97a 100644 --- a/docs/api/pleroma_api.md +++ b/docs/api/pleroma_api.md @@ -133,7 +133,7 @@ See [Admin-API](Admin-API.md) * Params (mutually exclusive): * `id`: a single notification id to read * `max_id`: read all notifications up to this id -* Response: Notification entity/Array of Notification entities. In case of `max_id`, only the first 80 notifications will be returned. +* Response: Notification entity/Array of Notification entities that were read. In case of `max_id`, only the first 80 read notifications will be returned. ## `/api/v1/pleroma/accounts/:id/subscribe` ### Subscribe to receive notifications for all statuses posted by a user From 3face454671bfdf2b850daf9dcb05468eb909e95 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Wed, 4 Sep 2019 14:16:56 +0300 Subject: [PATCH 11/14] Mastodon API: Add `pleroma.thread_muted` to Status entity Needed for pleroma-fe!941 --- CHANGELOG.md | 1 + docs/api/differences_in_mastoapi_responses.md | 1 + .../web/mastodon_api/views/status_view.ex | 3 ++- .../mastodon_api/views/status_view_test.exs | 21 ++++++++++++++++++- 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40f4580f7..a414ba5e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - **Breaking:** `/api/pleroma/notifications/read` is moved to `/api/v1/pleroma/notifications/read` and now supports `max_id` and responds with Mastodon API entities. - Configuration: OpenGraph and TwitterCard providers enabled by default - Configuration: Filter.AnonymizeFilename added ability to retain file extension with custom text +- Mastodon API: `pleroma.thread_muted` key in the Status entity - Federation: Return 403 errors when trying to request pages from a user's follower/following collections if they have `hide_followers`/`hide_follows` set - NodeInfo: Return `skipThreadContainment` in `metadata` for the `skip_thread_containment` option - NodeInfo: Return `mailerEnabled` in `metadata` diff --git a/docs/api/differences_in_mastoapi_responses.md b/docs/api/differences_in_mastoapi_responses.md index f34e3dd72..02f90f3e8 100644 --- a/docs/api/differences_in_mastoapi_responses.md +++ b/docs/api/differences_in_mastoapi_responses.md @@ -26,6 +26,7 @@ Has these additional fields under the `pleroma` object: - `content`: a map consisting of alternate representations of the `content` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain` - `spoiler_text`: a map consisting of alternate representations of the `spoiler_text` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain` - `expires_at`: a datetime (iso8601) that states when the post will expire (be deleted automatically), or empty if the post won't expire +- `thread_muted`: true if the thread the post belongs to is muted ## Attachments diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index a4ee0b5dd..4c3c8c564 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -299,7 +299,8 @@ def render("status.json", %{activity: %{data: %{"object" => _object}} = activity content: %{"text/plain" => content_plaintext}, spoiler_text: %{"text/plain" => summary_plaintext}, expires_at: expires_at, - direct_conversation_id: direct_conversation_id + direct_conversation_id: direct_conversation_id, + thread_muted: thread_muted? } } end diff --git a/test/web/mastodon_api/views/status_view_test.exs b/test/web/mastodon_api/views/status_view_test.exs index 1b6beb6d2..90451cbdc 100644 --- a/test/web/mastodon_api/views/status_view_test.exs +++ b/test/web/mastodon_api/views/status_view_test.exs @@ -150,7 +150,8 @@ test "a note activity" do content: %{"text/plain" => HtmlSanitizeEx.strip_tags(object_data["content"])}, spoiler_text: %{"text/plain" => HtmlSanitizeEx.strip_tags(object_data["summary"])}, expires_at: nil, - direct_conversation_id: nil + direct_conversation_id: nil, + thread_muted: false } } @@ -173,6 +174,24 @@ test "tells if the message is muted for some reason" do assert status.muted == true end + test "tells if the message is thread muted" do + user = insert(:user) + other_user = insert(:user) + + {:ok, user} = User.mute(user, other_user) + + {:ok, activity} = CommonAPI.post(other_user, %{"status" => "test"}) + status = StatusView.render("status.json", %{activity: activity, for: user}) + + assert status.pleroma.thread_muted == false + + {:ok, activity} = CommonAPI.add_mute(user, activity) + + status = StatusView.render("status.json", %{activity: activity, for: user}) + + assert status.pleroma.thread_muted == true + end + test "tells if the status is bookmarked" do user = insert(:user) From 053b17f57ecd9e1c3f82118e2a5e5c3b2937969d Mon Sep 17 00:00:00 2001 From: Ashlynn Anderson Date: Wed, 4 Sep 2019 14:56:26 -0400 Subject: [PATCH 12/14] Switch to alpine:3.9 to avoid dlsym errors --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 59a352bbc..c61dcfde9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,7 +12,7 @@ RUN apk add git gcc g++ musl-dev make &&\ mkdir release &&\ mix release --path release -FROM alpine:latest +FROM alpine:3.9 ARG HOME=/opt/pleroma ARG DATA=/var/lib/pleroma From 558969a0fd7f64387e59a54b5733d63d3a46a031 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Thu, 5 Sep 2019 08:32:49 +0300 Subject: [PATCH 13/14] Do not crash if one notification failed to render --- CHANGELOG.md | 1 + lib/pleroma/web/mastodon_api/views/notification_view.ex | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40f4580f7..80aed3491 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Federation/MediaProxy not working with instances that have wrong certificate order - Mastodon API: Handling of search timeouts (`/api/v1/search` and `/api/v2/search`) - Mastodon API: Embedded relationships not being properly rendered in the Account entity of Status entity +- Mastodon API: Notifications endpoint crashing if one notification failed to render - Mastodon API: follower/following counters not being nullified, when `hide_follows`/`hide_followers` is set - Mastodon API: `muted` in the Status entity, using author's account to determine if the tread was muted - Mastodon API: Add `account_id`, `type`, `offset`, and `limit` to search API (`/api/v1/search` and `/api/v2/search`) diff --git a/lib/pleroma/web/mastodon_api/views/notification_view.ex b/lib/pleroma/web/mastodon_api/views/notification_view.ex index 27e9cab06..ec8eadcaa 100644 --- a/lib/pleroma/web/mastodon_api/views/notification_view.ex +++ b/lib/pleroma/web/mastodon_api/views/notification_view.ex @@ -14,7 +14,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do alias Pleroma.Web.MastodonAPI.StatusView def render("index.json", %{notifications: notifications, for: user}) do - render_many(notifications, NotificationView, "show.json", %{for: user}) + safe_render_many(notifications, NotificationView, "show.json", %{for: user}) end def render("show.json", %{ From b312ca3d528305ebc3b0c72799af535a406ce251 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Thu, 5 Sep 2019 11:58:02 +0300 Subject: [PATCH 14/14] Mastodon API Poll view: Fix handling of polls without an end date --- CHANGELOG.md | 1 + .../web/mastodon_api/views/status_view.ex | 31 +++++++++++++------ .../tesla_mock/misskey_poll_no_end_date.json | 1 + test/fixtures/tesla_mock/sjw.json | 1 + test/support/http_request_mock.ex | 12 +++++++ .../mastodon_api/views/status_view_test.exs | 8 +++++ 6 files changed, 44 insertions(+), 10 deletions(-) create mode 100644 test/fixtures/tesla_mock/misskey_poll_no_end_date.json create mode 100644 test/fixtures/tesla_mock/sjw.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f4e7a132..fbbaf18f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - `federation_incoming_replies_max_depth` option being ignored in certain cases - Federation/MediaProxy not working with instances that have wrong certificate order - Mastodon API: Handling of search timeouts (`/api/v1/search` and `/api/v2/search`) +- Mastodon API: Misskey's endless polls being unable to render - Mastodon API: Embedded relationships not being properly rendered in the Account entity of Status entity - Mastodon API: Notifications endpoint crashing if one notification failed to render - Mastodon API: follower/following counters not being nullified, when `hide_follows`/`hide_followers` is set diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 4c3c8c564..e71083b91 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -385,16 +385,27 @@ def render("poll.json", %{object: object} = opts) do end if options do - end_time = - (object.data["closed"] || object.data["endTime"]) - |> NaiveDateTime.from_iso8601!() + {end_time, expired} = + case object.data["closed"] || object.data["endTime"] do + end_time when is_binary(end_time) -> + end_time = + (object.data["closed"] || object.data["endTime"]) + |> NaiveDateTime.from_iso8601!() - expired = - end_time - |> NaiveDateTime.compare(NaiveDateTime.utc_now()) - |> case do - :lt -> true - _ -> false + expired = + end_time + |> NaiveDateTime.compare(NaiveDateTime.utc_now()) + |> case do + :lt -> true + _ -> false + end + + end_time = Utils.to_masto_date(end_time) + + {end_time, expired} + + _ -> + {nil, false} end voted = @@ -421,7 +432,7 @@ def render("poll.json", %{object: object} = opts) do # Mastodon uses separate ids for polls, but an object can't have # more than one poll embedded so object id is fine id: to_string(object.id), - expires_at: Utils.to_masto_date(end_time), + expires_at: end_time, expired: expired, multiple: multiple, votes_count: votes_count, diff --git a/test/fixtures/tesla_mock/misskey_poll_no_end_date.json b/test/fixtures/tesla_mock/misskey_poll_no_end_date.json new file mode 100644 index 000000000..0e08de4de --- /dev/null +++ b/test/fixtures/tesla_mock/misskey_poll_no_end_date.json @@ -0,0 +1 @@ +{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1",{"Hashtag":"as:Hashtag"}],"id":"https://skippers-bin.com/notes/7x9tmrp97i","type":"Question","attributedTo":"https://skippers-bin.com/users/7v1w1r8ce6","summary":null,"content":"

@march@marchgenso.me How are your notifications now?
リモートで結果を表示

","_misskey_content":"@march@marchgenso.me How are your notifications now?\n[リモートで結果を表示](https://skippers-bin.com/notes/7x9tmrp97i)","published":"2019-09-05T05:35:32.541Z","to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://skippers-bin.com/users/7v1w1r8ce6/followers","https://marchgenso.me/users/march"],"inReplyTo":null,"attachment":[],"sensitive":false,"tag":[{"type":"Mention","href":"https://marchgenso.me/users/march","name":"@march@marchgenso.me"}],"_misskey_fallback_content":"

@march@marchgenso.me How are your notifications now?
リモートで結果を表示
----------------------------------------
0: Working
1: Broken af
----------------------------------------
番号を返信して投票

","endTime":null,"oneOf":[{"type":"Note","name":"Working","replies":{"type":"Collection","totalItems":0}},{"type":"Note","name":"Broken af","replies":{"type":"Collection","totalItems":1}}]} \ No newline at end of file diff --git a/test/fixtures/tesla_mock/sjw.json b/test/fixtures/tesla_mock/sjw.json new file mode 100644 index 000000000..ff64478d3 --- /dev/null +++ b/test/fixtures/tesla_mock/sjw.json @@ -0,0 +1 @@ +{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1",{"Hashtag":"as:Hashtag"}],"type":"Person","id":"https://skippers-bin.com/users/7v1w1r8ce6","inbox":"https://skippers-bin.com/users/7v1w1r8ce6/inbox","outbox":"https://skippers-bin.com/users/7v1w1r8ce6/outbox","followers":"https://skippers-bin.com/users/7v1w1r8ce6/followers","following":"https://skippers-bin.com/users/7v1w1r8ce6/following","featured":"https://skippers-bin.com/users/7v1w1r8ce6/collections/featured","sharedInbox":"https://skippers-bin.com/inbox","endpoints":{"sharedInbox":"https://skippers-bin.com/inbox"},"url":"https://skippers-bin.com/@sjw","preferredUsername":"sjw","name":"It's ya boi sjw :verified:","summary":"

Admin of skippers-bin.com and neckbeard.xyz
For the most part I'm just a normal user. I mostly post animu, lewds, may-mays, and shitposts.

Not an alt of
@sjw@neckbeard.xyz but another main.

Email/XMPP: neckbeard@rape.lol
PGP: d016 b622 75ba bcbc 5b3a fced a7d9 4824 0eb3 9c4e

","icon":{"type":"Image","url":"https://skippers-bin.com/files/webpublic-21b17f5b-3a83-4f50-8d4f-eda92066aa26","sensitive":false},"image":{"type":"Image","url":"https://skippers-bin.com/files/webpublic-1cd7f961-421e-4c31-aa03-74fb82584308","sensitive":false},"tag":[{"id":"https://skippers-bin.com/emojis/verified","type":"Emoji","name":":verified:","updated":"2019-07-12T02:16:12.088Z","icon":{"type":"Image","mediaType":"image/png","url":"https://skippers-bin.com/files/webpublic-dd10b435-6dad-4602-938b-f69ec0a19f2c"}}],"manuallyApprovesFollowers":false,"publicKey":{"id":"https://skippers-bin.com/users/7v1w1r8ce6/publickey","type":"Key","owner":"https://skippers-bin.com/users/7v1w1r8ce6","publicKeyPem":"-----BEGIN RSA PUBLIC KEY-----\nMIICCgKCAgEAvmp71/A6Oxe1UW/44HK0juAJhrjv9gYhaoslaS9K1FB+BHfIjaE9\n9+W2SKRLnVNYNFSN4JJrSGhX5RUjAsf4tcdRDVcmHl7tp2sgOAZeZz5geULm2sJQ\nwElnGk34jT/xCfX+w/O+7DuX31sU7ZK0B2P7ulNGDQXhrzVO0RMx7HhNcsFcusno\n3kmPyyPT1l+PbM2UNWms599/3yicKtuOzMgzxNeXvuHYtAO19txyPiOeYckQOMmT\nwEVIxypgCgNQ0MNtPLPKQTwOgVbvnN7MN+h3esKeKDcPcGQySkbkjZPaVnA6xCQf\nj58c19wqdCfAS4Effo5/bxVmhLpe0l9HYpV7IMasv2LhFntmSmAxBQzhdz0oTYb1\naNqiyfZdClnzutOiKcrFppADo4rZH9Z1WlPHapahrKbF0GRPN8DjSUsoBxfY9wZs\ntlL056hT4o+EFHYrRGo7KP6X/6aQ9sSsmpE08aVpVuXdwuaoaDlW1KrJ0oOk4lZw\nUNXvjEaN3c+VQAw2CNvkAqLuwrjnw7MdcxEGodEXb6s8VvoSOaiDqT7cexSaZe0R\nliCe/3dqFXpX1UrgRiryI4yc1BrEJIGTanchmP2aUJ2R2pccFsREp23C3vMN3M5b\nHw7fvKbUQHyf6lhRoLCOSCz1xaPutaMJmpwLuJo4wPCHGg9QFBYsqxcCAwEAAQ==\n-----END RSA PUBLIC KEY-----\n"},"isCat":true} diff --git a/test/support/http_request_mock.ex b/test/support/http_request_mock.ex index 05eebbe9b..231e7c498 100644 --- a/test/support/http_request_mock.ex +++ b/test/support/http_request_mock.ex @@ -992,6 +992,18 @@ def get("http://example.com/rel_me/null", _, _, _) do {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/rel_me_null.html")}} end + def get("https://skippers-bin.com/notes/7x9tmrp97i", _, _, _) do + {:ok, + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/tesla_mock/misskey_poll_no_end_date.json") + }} + end + + def get("https://skippers-bin.com/users/7v1w1r8ce6", _, _, _) do + {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/sjw.json")}} + end + def get(url, query, body, headers) do {:error, "Mock response not implemented for GET #{inspect(url)}, #{query}, #{inspect(body)}, #{ diff --git a/test/web/mastodon_api/views/status_view_test.exs b/test/web/mastodon_api/views/status_view_test.exs index 90451cbdc..fcdd7fbcb 100644 --- a/test/web/mastodon_api/views/status_view_test.exs +++ b/test/web/mastodon_api/views/status_view_test.exs @@ -551,6 +551,14 @@ test "detects vote status" do assert Enum.at(result[:options], 1)[:votes_count] == 1 assert Enum.at(result[:options], 2)[:votes_count] == 1 end + + test "does not crash on polls with no end date" do + object = Object.normalize("https://skippers-bin.com/notes/7x9tmrp97i") + result = StatusView.render("poll.json", %{object: object}) + + assert result[:expires_at] == nil + assert result[:expired] == false + end end test "embeds a relationship in the account" do