From f16c2e0b1b524fa9fc6a3733dd47e1d82f199fc7 Mon Sep 17 00:00:00 2001 From: William Pitcock Date: Tue, 6 Nov 2018 22:50:43 +0000 Subject: [PATCH 1/4] notification: add Notification.set_read_up_to() --- lib/pleroma/notification.ex | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index e0dcd9823..75d7461e4 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -42,6 +42,20 @@ def for_user(user, opts \\ %{}) do Repo.all(query) end + def set_read_up_to(%{id: user_id} = _user, id) do + query = + from( + n in Notification, + where: n.user_id == ^user_id, + where: n.id <= ^id, + update: [ + set: [seen: true] + ] + ) + + Repo.update_all(query, []) + end + def get(%{id: user_id} = _user, id) do query = from( From 50bf17465138cbd81117404231b22b3891cb67c3 Mon Sep 17 00:00:00 2001 From: William Pitcock Date: Tue, 6 Nov 2018 23:02:55 +0000 Subject: [PATCH 2/4] tests: add tests for Notification.set_read_up_to() --- test/notification_test.exs | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/test/notification_test.exs b/test/notification_test.exs index d86b5c1ab..79290ac78 100644 --- a/test/notification_test.exs +++ b/test/notification_test.exs @@ -121,6 +121,41 @@ test "it clears all notifications belonging to the user" do end end + describe "set_read_up_to()" do + test "it sets all notifications as read up to a specified notification ID" do + user = insert(:user) + other_user = insert(:user) + + {:ok, activity} = + TwitterAPI.create_status(user, %{ + "status" => "hey @#{other_user.nickname}!" + }) + + {:ok, activity} = + TwitterAPI.create_status(user, %{ + "status" => "hey again @#{other_user.nickname}!" + }) + + [n2, n1] = notifs = Notification.for_user(other_user) + assert length(notifs) == 2 + + assert n2.id > n1.id + + {:ok, activity} = + TwitterAPI.create_status(user, %{ + "status" => "hey yet again @#{other_user.nickname}!" + }) + + Notification.set_read_up_to(other_user, n2.id) + + [n3, n2, n1] = notifs = Notification.for_user(other_user) + + assert n1.seen == true + assert n2.seen == true + assert n3.seen == false + end + end + describe "notification lifecycle" do test "liking an activity results in 1 notification, then 0 if the activity is deleted" do user = insert(:user) From b2105a31316d371733a75322bbf60868700f037d Mon Sep 17 00:00:00 2001 From: William Pitcock Date: Tue, 6 Nov 2018 23:07:13 +0000 Subject: [PATCH 3/4] twitterapi: add notification read endpoint --- lib/pleroma/web/router.ex | 4 ++++ .../web/twitter_api/twitter_api_controller.ex | 13 +++++++++++++ 2 files changed, 17 insertions(+) diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index b461def82..06d0f0623 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -272,6 +272,10 @@ defmodule Pleroma.Web.Router do get("/statuses/mentions_timeline", TwitterAPI.Controller, :mentions_timeline) get("/qvitter/statuses/notifications", TwitterAPI.Controller, :notifications) + # XXX: this is really a pleroma API, but we want to keep the pleroma namespace clean + # for now. + post("/qvitter/statuses/notifications/read", TwitterAPI.Controller, :notifications_read) + post("/statuses/update", TwitterAPI.Controller, :status_update) post("/statuses/retweet/:id", TwitterAPI.Controller, :retweet) post("/statuses/unretweet/:id", TwitterAPI.Controller, :unretweet) diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex index 83d725f13..727469a66 100644 --- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex +++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex @@ -133,6 +133,19 @@ def notifications(%{assigns: %{user: user}} = conn, params) do |> render(NotificationView, "notification.json", %{notifications: notifications, for: user}) end + def notifications_read(%{assigns: %{user: user}} = conn, %{"latest_id" => latest_id} = params) do + Notification.set_read_up_to(user, latest_id) + + notifications = Notification.for_user(user, params) + + conn + |> render(NotificationView, "notification.json", %{notifications: notifications, for: user}) + end + + def notifications_read(%{assigns: %{user: user}} = conn, _) do + bad_request_reply(conn, "You need to specify latest_id") + end + def follow(%{assigns: %{user: user}} = conn, params) do case TwitterAPI.follow(user, params) do {:ok, user, followed, _activity} -> From d675b8a16f84728134b09340a78db9e77f87839c Mon Sep 17 00:00:00 2001 From: William Pitcock Date: Tue, 6 Nov 2018 23:25:16 +0000 Subject: [PATCH 4/4] tests: add tests for twitterapi endpoint --- .../twitter_api_controller_test.exs | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/test/web/twitter_api/twitter_api_controller_test.exs b/test/web/twitter_api/twitter_api_controller_test.exs index b64f416e3..13480c21b 100644 --- a/test/web/twitter_api/twitter_api_controller_test.exs +++ b/test/web/twitter_api/twitter_api_controller_test.exs @@ -331,6 +331,56 @@ test "with credentials", %{conn: conn, user: current_user} do end end + describe "POST /api/qvitter/statuses/notifications/read" do + setup [:valid_user] + + test "without valid credentials", %{conn: conn} do + conn = post(conn, "/api/qvitter/statuses/notifications/read", %{"latest_id" => 1_234_567}) + assert json_response(conn, 403) == %{"error" => "Invalid credentials."} + end + + test "with credentials, without any params", %{conn: conn, user: current_user} do + conn = + conn + |> with_credentials(current_user.nickname, "test") + |> post("/api/qvitter/statuses/notifications/read") + + assert json_response(conn, 400) == %{ + "error" => "You need to specify latest_id", + "request" => "/api/qvitter/statuses/notifications/read" + } + end + + test "with credentials, with params", %{conn: conn, user: current_user} do + other_user = insert(:user) + + {:ok, _activity} = + ActivityBuilder.insert(%{"to" => [current_user.ap_id]}, %{user: other_user}) + + response_conn = + conn + |> with_credentials(current_user.nickname, "test") + |> get("/api/qvitter/statuses/notifications.json") + + [notification] = response = json_response(response_conn, 200) + + assert length(response) == 1 + + assert notification["is_seen"] == 0 + + response_conn = + conn + |> with_credentials(current_user.nickname, "test") + |> post("/api/qvitter/statuses/notifications/read", %{"latest_id" => notification["id"]}) + + [notification] = response = json_response(response_conn, 200) + + assert length(response) == 1 + + assert notification["is_seen"] == 1 + end + end + describe "GET /statuses/user_timeline.json" do setup [:valid_user]