From c2b6c1b089a813cf8c7cbd54c0f80bee4985522c Mon Sep 17 00:00:00 2001 From: rinpatch Date: Wed, 4 Sep 2019 11:33:08 +0300 Subject: [PATCH 1/4] 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 2/4] 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 3/4] 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 4/4] 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