From e6a78c6ed0925c27ea4d194c0e52ab07542c444e Mon Sep 17 00:00:00 2001 From: eal Date: Fri, 10 Nov 2017 15:24:39 +0200 Subject: [PATCH] MastoAPI: Add notification get, clear and dismiss. --- lib/pleroma/notification.ex | 31 ++++++++ .../mastodon_api/mastodon_api_controller.ex | 65 ++++++++++++----- lib/pleroma/web/router.ex | 3 + test/notification_test.exs | 61 ++++++++++++++++ .../mastodon_api_controller_test.exs | 71 ++++++++++++++++++- 5 files changed, 213 insertions(+), 18 deletions(-) diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index 00a382f31..039cc7312 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -36,6 +36,37 @@ def for_user(user, opts \\ %{}) do Repo.all(query) end + def get(%{id: user_id} = _user, id) do + query = from n in Notification, + where: n.id == ^id, + preload: [:activity] + + notification = Repo.one(query) + case notification do + %{user_id: ^user_id} -> + {:ok, notification} + _ -> + {:error, "Cannot get notification"} + end + end + + def clear(user) do + query = from n in Notification, + where: n.user_id == ^user.id + + Repo.delete_all(query) + end + + def dismiss(%{id: user_id} = _user, id) do + notification = Repo.get(Notification, id) + case notification do + %{user_id: ^user_id} -> + Repo.delete(notification) + _ -> + {:error, "Cannot dismiss notification"} + end + end + def create_notifications(%Activity{id: id, data: %{"to" => to, "type" => type}} = activity) when type in ["Create", "Like", "Announce", "Follow"] do users = User.get_notified_from_activity(activity) diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index feaf9a900..d95b18315 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -193,23 +193,8 @@ def unfav_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do def notifications(%{assigns: %{user: user}} = conn, params) do notifications = Notification.for_user(user, params) - result = Enum.map(notifications, fn (%{id: id, activity: activity, inserted_at: created_at}) -> - actor = User.get_cached_by_ap_id(activity.data["actor"]) - created_at = NaiveDateTime.to_iso8601(created_at) - |> String.replace(~r/(\.\d+)?$/, ".000Z", global: false) - case activity.data["type"] do - "Create" -> - %{id: id, type: "mention", created_at: created_at, account: AccountView.render("account.json", %{user: actor}), status: StatusView.render("status.json", %{activity: activity, for: user})} - "Like" -> - liked_activity = Activity.get_create_activity_by_object_ap_id(activity.data["object"]) - %{id: id, type: "favourite", created_at: created_at, account: AccountView.render("account.json", %{user: actor}), status: StatusView.render("status.json", %{activity: liked_activity, for: user})} - "Announce" -> - announced_activity = Activity.get_create_activity_by_object_ap_id(activity.data["object"]) - %{id: id, type: "reblog", created_at: created_at, account: AccountView.render("account.json", %{user: actor}), status: StatusView.render("status.json", %{activity: announced_activity, for: user})} - "Follow" -> - %{id: id, type: "follow", created_at: created_at, account: AccountView.render("account.json", %{user: actor})} - _ -> nil - end + result = Enum.map(notifications, fn x -> + render_notification(user, x) end) |> Enum.filter(&(&1)) @@ -218,6 +203,33 @@ def notifications(%{assigns: %{user: user}} = conn, params) do |> json(result) end + def get_notification(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do + with {:ok, notification} <- Notification.get(user, id) do + json(conn, render_notification(user, notification)) + else + {:error, reason} -> + conn + |> put_resp_content_type("application/json") + |> send_resp(403, Poison.encode!(%{"error" => reason})) + end + end + + def clear_notifications(%{assigns: %{user: user}} = conn, _params) do + Notification.clear(user) + json(conn, %{}) + end + + def dismiss_notification(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do + with {:ok, _notif} <- Notification.dismiss(user, id) do + json(conn, %{}) + else + {:error, reason} -> + conn + |> put_resp_content_type("application/json") + |> send_resp(403, Poison.encode!(%{"error" => reason})) + end + end + def relationships(%{assigns: %{user: user}} = conn, %{"id" => id}) do id = List.wrap(id) q = from u in User, @@ -408,4 +420,23 @@ def empty_array(conn, _) do Logger.debug("Unimplemented, returning an empty array") json(conn, []) end + + defp render_notification(user, %{id: id, activity: activity, inserted_at: created_at} = _params) do + actor = User.get_cached_by_ap_id(activity.data["actor"]) + created_at = NaiveDateTime.to_iso8601(created_at) + |> String.replace(~r/(\.\d+)?$/, ".000Z", global: false) + case activity.data["type"] do + "Create" -> + %{id: id, type: "mention", created_at: created_at, account: AccountView.render("account.json", %{user: actor}), status: StatusView.render("status.json", %{activity: activity, for: user})} + "Like" -> + liked_activity = Activity.get_create_activity_by_object_ap_id(activity.data["object"]) + %{id: id, type: "favourite", created_at: created_at, account: AccountView.render("account.json", %{user: actor}), status: StatusView.render("status.json", %{activity: liked_activity, for: user})} + "Announce" -> + announced_activity = Activity.get_create_activity_by_object_ap_id(activity.data["object"]) + %{id: id, type: "reblog", created_at: created_at, account: AccountView.render("account.json", %{user: actor}), status: StatusView.render("status.json", %{activity: announced_activity, for: user})} + "Follow" -> + %{id: id, type: "follow", created_at: created_at, account: AccountView.render("account.json", %{user: actor})} + _ -> nil + end + end end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 0a0aea966..efd37ede2 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -82,7 +82,10 @@ def user_fetcher(username) do post "/statuses/:id/favourite", MastodonAPIController, :fav_status post "/statuses/:id/unfavourite", MastodonAPIController, :unfav_status + post "/notifications/clear", MastodonAPIController, :clear_notifications + post "/notifications/dismiss", MastodonAPIController, :dismiss_notification get "/notifications", MastodonAPIController, :notifications + get "/notifications/:id", MastodonAPIController, :get_notification post "/media", MastodonAPIController, :upload end diff --git a/test/notification_test.exs b/test/notification_test.exs index 77fdb532f..eee1c9fa3 100644 --- a/test/notification_test.exs +++ b/test/notification_test.exs @@ -31,4 +31,65 @@ test "it doesn't create a notification for user if the user blocks the activity assert nil == Notification.create_notification(activity, user) end end + + describe "get notification" do + test "it gets a notification that belongs to the user" do + user = insert(:user) + other_user = insert(:user) + + {:ok, activity} = TwitterAPI.create_status(user, %{"status" => "hey @#{other_user.nickname}"}) + {:ok, [notification]} = Notification.create_notifications(activity) + {:ok, notification} = Notification.get(other_user, notification.id) + + assert notification.user_id == other_user.id + end + + test "it returns error if the notification doesn't belong to the user" do + user = insert(:user) + other_user = insert(:user) + + {:ok, activity} = TwitterAPI.create_status(user, %{"status" => "hey @#{other_user.nickname}"}) + {:ok, [notification]} = Notification.create_notifications(activity) + {:error, notification} = Notification.get(user, notification.id) + end + end + + describe "dismiss notification" do + test "it dismisses a notification that belongs to the user" do + user = insert(:user) + other_user = insert(:user) + + {:ok, activity} = TwitterAPI.create_status(user, %{"status" => "hey @#{other_user.nickname}"}) + {:ok, [notification]} = Notification.create_notifications(activity) + {:ok, notification} = Notification.dismiss(other_user, notification.id) + + assert notification.user_id == other_user.id + end + + test "it returns error if the notification doesn't belong to the user" do + user = insert(:user) + other_user = insert(:user) + + {:ok, activity} = TwitterAPI.create_status(user, %{"status" => "hey @#{other_user.nickname}"}) + {:ok, [notification]} = Notification.create_notifications(activity) + {:error, notification} = Notification.dismiss(user, notification.id) + end + end + + describe "clear notification" do + test "it clears all notifications belonging to the user" do + user = insert(:user) + other_user = insert(:user) + third_user = insert(:user) + + {:ok, activity} = TwitterAPI.create_status(user, %{"status" => "hey @#{other_user.nickname} and @#{third_user.nickname} !"}) + {:ok, _notifs} = Notification.create_notifications(activity) + {:ok, activity} = TwitterAPI.create_status(user, %{"status" => "hey again @#{other_user.nickname} and @#{third_user.nickname} !"}) + {:ok, _notifs} = Notification.create_notifications(activity) + Notification.clear(other_user) + + assert Notification.for_user(other_user) == [] + assert Notification.for_user(third_user) != [] + end + end end diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs index d118026eb..e876b0af4 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -2,7 +2,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do use Pleroma.Web.ConnCase alias Pleroma.Web.TwitterAPI.TwitterAPI - alias Pleroma.{Repo, User, Activity} + alias Pleroma.{Repo, User, Activity, Notification} alias Pleroma.Web.{OStatus, CommonAPI} import Pleroma.Factory @@ -122,6 +122,75 @@ test "when you didn't create it", %{conn: conn} do end end + describe "notifications" do + test "list of notifications", %{conn: conn} do + user = insert(:user) + other_user = insert(:user) + + {:ok, activity} = TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"}) + {:ok, [notification]} = Notification.create_notifications(activity) + + conn = conn + |> assign(:user, user) + |> get("/api/v1/notifications") + + expected_response = "hi @#{user.nickname}" + assert [%{"status" => %{"content" => response}} | _rest] = json_response(conn, 200) + assert response == expected_response + end + + test "getting a single notification", %{conn: conn} do + user = insert(:user) + other_user = insert(:user) + + {:ok, activity} = TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"}) + {:ok, [notification]} = Notification.create_notifications(activity) + + conn = conn + |> assign(:user, user) + |> get("/api/v1/notifications/#{notification.id}") + + expected_response = "hi @#{user.nickname}" + assert %{"status" => %{"content" => response}} = json_response(conn, 200) + assert response == expected_response + end + + test "dismissing a single notification", %{conn: conn} do + user = insert(:user) + other_user = insert(:user) + + {:ok, activity} = TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"}) + {:ok, [notification]} = Notification.create_notifications(activity) + + conn = conn + |> assign(:user, user) + |> post("/api/v1/notifications/dismiss", %{"id" => notification.id}) + + assert %{} = json_response(conn, 200) + end + + test "clearing all notifications", %{conn: conn} do + user = insert(:user) + other_user = insert(:user) + + {:ok, activity} = TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"}) + {:ok, [notification]} = Notification.create_notifications(activity) + + conn = conn + |> assign(:user, user) + |> post("/api/v1/notifications/clear") + + assert %{} = json_response(conn, 200) + + conn = build_conn() + |> assign(:user, user) + |> get("/api/v1/notifications") + + assert all = json_response(conn, 200) + assert all == [] + end + end + describe "reblogging" do test "reblogs and returns the reblogged status", %{conn: conn} do activity = insert(:note_activity)