diff options
| -rw-r--r-- | lib/pleroma/notification.ex | 1 | ||||
| -rw-r--r-- | lib/pleroma/subscription_notification.ex | 266 | ||||
| -rw-r--r-- | lib/pleroma/web/activity_pub/activity_pub.ex | 2 | ||||
| -rw-r--r-- | lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex | 48 | ||||
| -rw-r--r-- | lib/pleroma/web/mastodon_api/mastodon_api.ex | 10 | ||||
| -rw-r--r-- | lib/pleroma/web/mastodon_api/views/subscription_notification_view.ex | 61 | ||||
| -rw-r--r-- | lib/pleroma/web/pleroma_api/pleroma_api_controller.ex | 26 | ||||
| -rw-r--r-- | lib/pleroma/web/push/impl.ex | 3 | ||||
| -rw-r--r-- | lib/pleroma/web/router.ex | 28 | ||||
| -rw-r--r-- | lib/pleroma/web/streamer.ex | 14 | ||||
| -rw-r--r-- | priv/repo/migrations/20190824195028_create_subscription_notifications.exs | 15 | ||||
| -rw-r--r-- | test/notification_test.exs | 12 | ||||
| -rw-r--r-- | test/web/mastodon_api/mastodon_api_controller_test.exs | 192 | ||||
| -rw-r--r-- | test/web/mastodon_api/mastodon_api_test.exs | 4 | 
14 files changed, 670 insertions, 12 deletions
| diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index b7c880c51..716d98733 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -228,7 +228,6 @@ defmodule Pleroma.Notification do        []        |> Utils.maybe_notify_to_recipients(activity)        |> Utils.maybe_notify_mentioned_recipients(activity) -      |> Utils.maybe_notify_subscribers(activity)        |> Enum.uniq()      User.get_users_from_set(recipients, local_only) diff --git a/lib/pleroma/subscription_notification.ex b/lib/pleroma/subscription_notification.ex new file mode 100644 index 000000000..7ae25a7b1 --- /dev/null +++ b/lib/pleroma/subscription_notification.ex @@ -0,0 +1,266 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.SubscriptionNotification do +  use Ecto.Schema + +  alias Pleroma.Activity +  alias Pleroma.Object +  alias Pleroma.Pagination +  alias Pleroma.Repo +  alias Pleroma.SubscriptionNotification +  alias Pleroma.User +  alias Pleroma.Web.CommonAPI.Utils +  alias Pleroma.Web.Push +  alias Pleroma.Web.Streamer + +  import Ecto.Query +  import Ecto.Changeset + +  @type t :: %__MODULE__{} + +  schema "subscription_notifications" do +    belongs_to(:user, User, type: Pleroma.FlakeId) +    belongs_to(:activity, Activity, type: Pleroma.FlakeId) + +    timestamps() +  end + +  def changeset(%SubscriptionNotification{} = notification, attrs) do +    cast(notification, attrs, []) +  end + +  def for_user_query(user, opts \\ []) do +    query = +      SubscriptionNotification +      |> where(user_id: ^user.id) +      |> where( +        [n, a], +        fragment( +          "? not in (SELECT ap_id FROM users WHERE info->'deactivated' @> 'true')", +          a.actor +        ) +      ) +      |> 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}) + +    if opts[:with_muted] do +      query +    else +      where(query, [n, a], a.actor not in ^user.info.muted_notifications) +      |> where([n, a], a.actor not in ^user.info.blocks) +      |> where( +        [n, a], +        fragment("substring(? from '.*://([^/]*)')", a.actor) not in ^user.info.domain_blocks +      ) +      |> join(:left, [n, a], tm in Pleroma.ThreadMute, +        on: tm.user_id == ^user.id and tm.context == fragment("?->>'context'", a.data) +      ) +      |> where([n, a, o, tm], is_nil(tm.user_id)) +    end +  end + +  def for_user(user, opts \\ %{}) do +    user +    |> for_user_query(opts) +    |> Pagination.fetch_paginated(opts) +  end + +  @doc """ +  Returns notifications for user received since given date. + +  ## Examples + +      iex> Pleroma.SubscriptionNotification.for_user_since(%Pleroma.User{}, ~N[2019-04-13 11:22:33]) +      [%Pleroma.SubscriptionNotification{}, %Pleroma.SubscriptionNotification{}] + +      iex> Pleroma.SubscriptionNotification.for_user_since(%Pleroma.User{}, ~N[2019-04-15 11:22:33]) +      [] +  """ +  @spec for_user_since(Pleroma.User.t(), NaiveDateTime.t()) :: [t()] +  def for_user_since(user, date) do +    from(n in for_user_query(user), +      where: n.updated_at > ^date +    ) +    |> Repo.all() +  end + +  def clear_up_to(%{id: user_id} = _user, id) do +    from( +      n in SubscriptionNotification, +      where: n.user_id == ^user_id, +      where: n.id <= ^id +    ) +    |> Repo.delete_all([]) +  end + +  def get(%{id: user_id} = _user, id) do +    query = +      from( +        n in SubscriptionNotification, +        where: n.id == ^id, +        join: activity in assoc(n, :activity), +        preload: [activity: activity] +      ) + +    notification = Repo.one(query) + +    case notification do +      %{user_id: ^user_id} -> +        {:ok, notification} + +      _ -> +        {:error, "Cannot get notification"} +    end +  end + +  def clear(user) do +    from(n in SubscriptionNotification, where: n.user_id == ^user.id) +    |> Repo.delete_all() +  end + +  def destroy_multiple(%{id: user_id} = _user, ids) do +    from(n in SubscriptionNotification, +      where: n.id in ^ids, +      where: n.user_id == ^user_id +    ) +    |> Repo.delete_all() +  end + +  def dismiss(%{id: user_id} = _user, id) do +    notification = Repo.get(SubscriptionNotification, id) + +    case notification do +      %{user_id: ^user_id} -> +        Repo.delete(notification) + +      _ -> +        {:error, "Cannot dismiss notification"} +    end +  end + +  def create_notifications(%Activity{data: %{"to" => _, "type" => "Create"}} = activity) do +    object = Object.normalize(activity) + +    unless object && object.data["type"] == "Answer" do +      users = get_notified_from_activity(activity) +      notifications = Enum.map(users, fn user -> create_notification(activity, user) end) +      {:ok, notifications} +    else +      {:ok, []} +    end +  end + +  def create_notifications(%Activity{data: %{"to" => _, "type" => type}} = activity) +      when type in ["Like", "Announce", "Follow"] do +    users = get_notified_from_activity(activity) +    notifications = Enum.map(users, fn user -> create_notification(activity, user) end) +    {:ok, notifications} +  end + +  def create_notifications(_), do: {:ok, []} + +  # TODO move to sql, too. +  def create_notification(%Activity{} = activity, %User{} = user) do +    unless skip?(activity, user) do +      notification = %SubscriptionNotification{user_id: user.id, activity: activity} +      {:ok, notification} = Repo.insert(notification) +      Streamer.stream("user", notification) +      Streamer.stream("user:subscription_notification", notification) +      Push.send(notification) +      notification +    end +  end + +  def get_notified_from_activity(activity, local_only \\ true) + +  def get_notified_from_activity( +        %Activity{data: %{"to" => _, "type" => type} = _data} = activity, +        local_only +      ) +      when type in ["Create", "Like", "Announce", "Follow"] do +    recipients = +      [] +      |> Utils.maybe_notify_subscribers(activity) +      |> Enum.uniq() + +    User.get_users_from_set(recipients, local_only) +  end + +  def get_notified_from_activity(_, _local_only), do: [] + +  @spec skip?(Activity.t(), User.t()) :: boolean() +  def skip?(activity, user) do +    [ +      :self, +      :followers, +      :follows, +      :non_followers, +      :non_follows, +      :recently_followed +    ] +    |> Enum.any?(&skip?(&1, activity, user)) +  end + +  @spec skip?(atom(), Activity.t(), User.t()) :: boolean() +  def skip?(:self, activity, user) do +    activity.data["actor"] == user.ap_id +  end + +  def skip?( +        :followers, +        activity, +        %{info: %{notification_settings: %{"followers" => false}}} = user +      ) do +    actor = activity.data["actor"] +    follower = User.get_cached_by_ap_id(actor) +    User.following?(follower, user) +  end + +  def skip?( +        :non_followers, +        activity, +        %{info: %{notification_settings: %{"non_followers" => false}}} = user +      ) do +    actor = activity.data["actor"] +    follower = User.get_cached_by_ap_id(actor) +    !User.following?(follower, user) +  end + +  def skip?(:follows, activity, %{info: %{notification_settings: %{"follows" => false}}} = user) do +    actor = activity.data["actor"] +    followed = User.get_cached_by_ap_id(actor) +    User.following?(user, followed) +  end + +  def skip?( +        :non_follows, +        activity, +        %{info: %{notification_settings: %{"non_follows" => false}}} = user +      ) do +    actor = activity.data["actor"] +    followed = User.get_cached_by_ap_id(actor) +    !User.following?(user, followed) +  end + +  def skip?(:recently_followed, %{data: %{"type" => "Follow"}} = activity, user) do +    actor = activity.data["actor"] + +    SubscriptionNotification.for_user(user) +    |> Enum.any?(fn +      %{activity: %{data: %{"type" => "Follow", "actor" => ^actor}}} -> true +      _ -> false +    end) +  end + +  def skip?(_, _, _), do: false +end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index d23ec66ac..bc9a7a2d6 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -12,6 +12,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do    alias Pleroma.Object.Fetcher    alias Pleroma.Pagination    alias Pleroma.Repo +  alias Pleroma.SubscriptionNotification    alias Pleroma.Upload    alias Pleroma.User    alias Pleroma.Web.ActivityPub.MRF @@ -148,6 +149,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do        PleromaJobQueue.enqueue(:background, Pleroma.Web.RichMedia.Helpers, [:fetch, activity])        Notification.create_notifications(activity) +      SubscriptionNotification.create_notifications(activity)        participations =          activity 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 060137b80..eefdb8c06 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -23,6 +23,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do    alias Pleroma.Repo    alias Pleroma.ScheduledActivity    alias Pleroma.Stats +  alias Pleroma.SubscriptionNotification    alias Pleroma.User    alias Pleroma.Web    alias Pleroma.Web.ActivityPub.ActivityPub @@ -39,6 +40,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do    alias Pleroma.Web.MastodonAPI.ReportView    alias Pleroma.Web.MastodonAPI.ScheduledActivityView    alias Pleroma.Web.MastodonAPI.StatusView +  alias Pleroma.Web.MastodonAPI.SubscriptionNotificationView    alias Pleroma.Web.MediaProxy    alias Pleroma.Web.OAuth.App    alias Pleroma.Web.OAuth.Authorization @@ -725,6 +727,28 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do      |> render("index.json", %{notifications: notifications, for: user})    end +  def subscription_notifications(%{assigns: %{user: user}} = conn, params) do +    notifications = MastodonAPI.get_subscription_notifications(user, params) + +    conn +    |> add_link_headers(:subscription_notifications, notifications) +    |> put_view(SubscriptionNotificationView) +    |> render("index.json", %{notifications: notifications, for: user}) +  end + +  def get_subscription_notification(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do +    with {:ok, notification} <- SubscriptionNotification.get(user, id) do +      conn +      |> put_view(SubscriptionNotificationView) +      |> render("show.json", %{subscription_notification: notification, for: user}) +    else +      {:error, reason} -> +        conn +        |> put_status(:forbidden) +        |> json(%{"error" => reason}) +    end +  end +    def get_notification(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do      with {:ok, notification} <- Notification.get(user, id) do        conn @@ -743,6 +767,11 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do      json(conn, %{})    end +  def clear_subscription_notifications(%{assigns: %{user: user}} = conn, _params) do +    SubscriptionNotification.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, %{}) @@ -754,11 +783,30 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do      end    end +  def dismiss_subscription_notification(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do +    with {:ok, _notif} <- SubscriptionNotification.dismiss(user, id) do +      json(conn, %{}) +    else +      {:error, reason} -> +        conn +        |> put_status(:forbidden) +        |> json(%{"error" => reason}) +    end +  end +    def destroy_multiple(%{assigns: %{user: user}} = conn, %{"ids" => ids} = _params) do      Notification.destroy_multiple(user, ids)      json(conn, %{})    end +  def destroy_multiple_subscription_notifications( +        %{assigns: %{user: user}} = conn, +        %{"ids" => ids} = _params +      ) do +    SubscriptionNotification.destroy_multiple(user, ids) +    json(conn, %{}) +  end +    def relationships(%{assigns: %{user: user}} = conn, %{"id" => id}) do      id = List.wrap(id)      q = from(u in User, where: u.id in ^id) diff --git a/lib/pleroma/web/mastodon_api/mastodon_api.ex b/lib/pleroma/web/mastodon_api/mastodon_api.ex index ac01d1ff3..6751e24d8 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api.ex @@ -10,6 +10,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do    alias Pleroma.Notification    alias Pleroma.Pagination    alias Pleroma.ScheduledActivity +  alias Pleroma.SubscriptionNotification    alias Pleroma.User    alias Pleroma.Web.CommonAPI @@ -62,6 +63,15 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do      |> Pagination.fetch_paginated(params)    end +  def get_subscription_notifications(user, params \\ %{}) do +    options = cast_params(params) + +    user +    |> SubscriptionNotification.for_user_query(options) +    |> restrict(:exclude_types, options) +    |> Pagination.fetch_paginated(params) +  end +    def get_scheduled_activities(user, params \\ %{}) do      user      |> ScheduledActivity.for_user_query() diff --git a/lib/pleroma/web/mastodon_api/views/subscription_notification_view.ex b/lib/pleroma/web/mastodon_api/views/subscription_notification_view.ex new file mode 100644 index 000000000..c6f0b5064 --- /dev/null +++ b/lib/pleroma/web/mastodon_api/views/subscription_notification_view.ex @@ -0,0 +1,61 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.SubscriptionNotificationView do +  use Pleroma.Web, :view + +  alias Pleroma.Activity +  # alias Pleroma.SubscriptionNotification +  alias Pleroma.User +  alias Pleroma.Web.CommonAPI +  alias Pleroma.Web.MastodonAPI.AccountView +  alias Pleroma.Web.MastodonAPI.SubscriptionNotificationView +  alias Pleroma.Web.MastodonAPI.StatusView + +  def render("index.json", %{notifications: notifications, for: user}) do +    safe_render_many(notifications, SubscriptionNotificationView, "show.json", %{for: user}) +  end + +  def render("show.json", %{ +        subscription_notification: %{activity: activity} = notification, +        for: user +      }) do +    actor = User.get_cached_by_ap_id(activity.data["actor"]) +    parent_activity = Activity.get_create_by_object_ap_id(activity.data["object"]) +    mastodon_type = Activity.mastodon_notification_type(activity) + +    response = %{ +      id: to_string(notification.id), +      type: mastodon_type, +      created_at: CommonAPI.Utils.to_masto_date(notification.inserted_at), +      account: AccountView.render("account.json", %{user: actor, for: user}) +    } + +    case mastodon_type do +      "mention" -> +        response +        |> Map.merge(%{ +          status: StatusView.render("status.json", %{activity: activity, for: user}) +        }) + +      "favourite" -> +        response +        |> Map.merge(%{ +          status: StatusView.render("status.json", %{activity: parent_activity, for: user}) +        }) + +      "reblog" -> +        response +        |> Map.merge(%{ +          status: StatusView.render("status.json", %{activity: parent_activity, for: user}) +        }) + +      "follow" -> +        response + +      _ -> +        nil +    end +  end +end diff --git a/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex b/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex index d17ccf84d..246b351dc 100644 --- a/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex @@ -9,6 +9,7 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do    alias Pleroma.Conversation.Participation    alias Pleroma.Notification +  alias Pleroma.SubscriptionNotification    alias Pleroma.Web.ActivityPub.ActivityPub    alias Pleroma.Web.MastodonAPI.ConversationView    alias Pleroma.Web.MastodonAPI.NotificationView @@ -86,4 +87,29 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do        |> render("index.json", %{notifications: notifications, for: user})      end    end + +  def delete_subscription_notification(%{assigns: %{user: user}} = conn, %{ +        "id" => notification_id +      }) do +    with {:ok, notification} <- SubscriptionNotification.dismiss(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_subscription_notification(%{assigns: %{user: user}} = conn, %{"max_id" => max_id}) do +    with notifications <- SubscriptionNotification.clear_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/push/impl.ex b/lib/pleroma/web/push/impl.ex index 35d3ff07c..7ea5607fa 100644 --- a/lib/pleroma/web/push/impl.ex +++ b/lib/pleroma/web/push/impl.ex @@ -9,6 +9,7 @@ defmodule Pleroma.Web.Push.Impl do    alias Pleroma.Notification    alias Pleroma.Object    alias Pleroma.Repo +  alias Pleroma.SubscriptionNotification    alias Pleroma.User    alias Pleroma.Web.Metadata.Utils    alias Pleroma.Web.Push.Subscription @@ -19,7 +20,7 @@ defmodule Pleroma.Web.Push.Impl do    @types ["Create", "Follow", "Announce", "Like"]    @doc "Performs sending notifications for user subscriptions" -  @spec perform(Notification.t()) :: list(any) | :error +  @spec perform(Notification.t() | SubscriptionNotification.t()) :: list(any) | :error    def perform(          %{            activity: %{data: %{"type" => activity_type}, id: activity_id} = activity, diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index b0464037e..dbd0deecd 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -300,11 +300,39 @@ defmodule Pleroma.Web.Router do        get("/bookmarks", MastodonAPIController, :bookmarks)        post("/notifications/clear", MastodonAPIController, :clear_notifications) + +      post( +        "/notifications/subscription/clear", +        MastodonAPIController, +        :clear_subscription_notifications +      ) +        post("/notifications/dismiss", MastodonAPIController, :dismiss_notification) + +      post( +        "/notifications/subscription/dismiss", +        MastodonAPIController, +        :dismiss_subscription_notification +      ) +        get("/notifications", MastodonAPIController, :notifications) +      get("/notifications/subscription", MastodonAPIController, :subscription_notifications)        get("/notifications/:id", MastodonAPIController, :get_notification) + +      get( +        "/notifications/subscription/:id", +        MastodonAPIController, +        :get_subscription_notification +      ) +        delete("/notifications/destroy_multiple", MastodonAPIController, :destroy_multiple) +      delete( +        "/notifications/subscription/destroy_multiple", +        MastodonAPIController, +        :destroy_multiple_subscription_notifications +      ) +        get("/scheduled_statuses", MastodonAPIController, :scheduled_statuses)        get("/scheduled_statuses/:id", MastodonAPIController, :show_scheduled_status) diff --git a/lib/pleroma/web/streamer.ex b/lib/pleroma/web/streamer.ex index 587c43f40..42d95e33a 100644 --- a/lib/pleroma/web/streamer.ex +++ b/lib/pleroma/web/streamer.ex @@ -10,6 +10,7 @@ defmodule Pleroma.Web.Streamer do    alias Pleroma.Conversation.Participation    alias Pleroma.Notification    alias Pleroma.Object +  alias Pleroma.SubscriptionNotification    alias Pleroma.User    alias Pleroma.Web.ActivityPub.ActivityPub    alias Pleroma.Web.ActivityPub.Visibility @@ -208,10 +209,17 @@ defmodule Pleroma.Web.Streamer do      |> Jason.encode!()    end -  @spec represent_notification(User.t(), Notification.t()) :: binary() -  defp represent_notification(%User{} = user, %Notification{} = notify) do +  @spec represent_notification(User.t(), Notification.t() | %SubscriptionNotification{}) :: +          binary() +  defp represent_notification(%User{} = user, notify) do +    event = +      case notify do +        %Notification{} -> "notification" +        %SubscriptionNotification{} -> "subscription_norification" +      end +      %{ -      event: "notification", +      event: event,        payload:          NotificationView.render(            "show.json", diff --git a/priv/repo/migrations/20190824195028_create_subscription_notifications.exs b/priv/repo/migrations/20190824195028_create_subscription_notifications.exs new file mode 100644 index 000000000..fcceb4386 --- /dev/null +++ b/priv/repo/migrations/20190824195028_create_subscription_notifications.exs @@ -0,0 +1,15 @@ +defmodule Pleroma.Repo.Migrations.CreateSubscriptionNotifications do +  use Ecto.Migration + +  def change do +    create_if_not_exists table(:subscription_notifications) do +      add(:user_id, references(:users, type: :uuid, on_delete: :delete_all)) +      add(:activity_id, references(:activities, type: :uuid, on_delete: :delete_all)) + +      timestamps() +    end + +    create_if_not_exists(index(:subscription_notifications, [:user_id])) +    create_if_not_exists(index(:subscription_notifications, ["id desc nulls last"])) +  end +end diff --git a/test/notification_test.exs b/test/notification_test.exs index 2a52dad8d..0e2635aad 100644 --- a/test/notification_test.exs +++ b/test/notification_test.exs @@ -32,16 +32,16 @@ defmodule Pleroma.NotificationTest do        assert other_notification.activity_id == activity.id      end -    test "it creates a notification for subscribed users" do +    test "it does not create a notification for subscribed users" do        user = insert(:user)        subscriber = insert(:user)        User.subscribe(subscriber, user)        {:ok, status} = CommonAPI.post(user, %{"status" => "Akariiiin"}) -      {:ok, [notification]} = Notification.create_notifications(status) +      {:ok, notifications} = Notification.create_notifications(status) -      assert notification.user_id == subscriber.id +      assert notifications == []      end      test "does not create a notification for subscribed users if status is a reply" do @@ -190,14 +190,16 @@ defmodule Pleroma.NotificationTest do        refute Notification.create_notification(activity_dupe, followed_user)      end -    test "it doesn't create duplicate notifications for follow+subscribed users" do +    test "it doesn't create notifications for follow+subscribed users" do        user = insert(:user)        subscriber = insert(:user)        {:ok, _, _, _} = CommonAPI.follow(subscriber, user)        User.subscribe(subscriber, user)        {:ok, status} = CommonAPI.post(user, %{"status" => "Akariiiin"}) -      {:ok, [_notif]} = Notification.create_notifications(status) +      {:ok, notifications} = Notification.create_notifications(status) + +      assert notifications == []      end      test "it doesn't create subscription notifications if the recipient cannot see the status" do diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs index 061c3a8ad..27a8366f7 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -13,6 +13,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do    alias Pleroma.Object    alias Pleroma.Repo    alias Pleroma.ScheduledActivity +  alias Pleroma.SubscriptionNotification    alias Pleroma.User    alias Pleroma.Web.ActivityPub.ActivityPub    alias Pleroma.Web.CommonAPI @@ -1273,6 +1274,197 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do      end    end +  describe "subscription_notifications" do +    setup do +      user = insert(:user) +      subscriber = insert(:user) + +      User.subscribe(subscriber, user) + +      {:ok, %{user: user, subscriber: subscriber}} +    end + +    test "list of notifications", %{conn: conn, user: user, subscriber: subscriber} do +      status_text = "Hello" +      {:ok, _activity} = CommonAPI.post(user, %{"status" => status_text}) + +      conn = +        conn +        |> assign(:user, subscriber) +        |> get("/api/v1/notifications/subscription") + +      assert [%{"status" => %{"content" => response}} | _rest] = json_response(conn, 200) +      assert response == status_text +    end + +    test "getting a single notification", %{conn: conn, user: user, subscriber: subscriber} do +      status_text = "Hello" + +      {:ok, _activity} = CommonAPI.post(user, %{"status" => status_text}) +      [notification] = Repo.all(SubscriptionNotification) + +      conn = +        conn +        |> assign(:user, subscriber) +        |> get("/api/v1/notifications/subscription/#{notification.id}") + +      assert %{"status" => %{"content" => response}} = json_response(conn, 200) +      assert response == status_text +    end + +    test "dismissing a single notification also deletes it", %{ +      conn: conn, +      user: user, +      subscriber: subscriber +    } do +      status_text = "Hello" +      {:ok, _activity} = CommonAPI.post(user, %{"status" => status_text}) + +      [notification] = Repo.all(SubscriptionNotification) + +      conn = +        conn +        |> assign(:user, subscriber) +        |> post("/api/v1/notifications/subscription/dismiss", %{"id" => notification.id}) + +      assert %{} = json_response(conn, 200) + +      assert Repo.all(SubscriptionNotification) == [] +    end + +    test "clearing all notifications also deletes them", %{ +      conn: conn, +      user: user, +      subscriber: subscriber +    } do +      status_text1 = "Hello" +      status_text2 = "Hello again" +      {:ok, _activity1} = CommonAPI.post(user, %{"status" => status_text1}) +      {:ok, _activity2} = CommonAPI.post(user, %{"status" => status_text2}) + +      conn = +        conn +        |> assign(:user, subscriber) +        |> post("/api/v1/notifications/subscription/clear") + +      assert %{} = json_response(conn, 200) + +      conn = +        build_conn() +        |> assign(:user, subscriber) +        |> get("/api/v1/notifications/subscription") + +      assert json_response(conn, 200) == [] + +      assert Repo.all(SubscriptionNotification) == [] +    end + +    test "paginates notifications using min_id, since_id, max_id, and limit", %{ +      conn: conn, +      user: user, +      subscriber: subscriber +    } do +      {:ok, activity1} = CommonAPI.post(user, %{"status" => "Hello 1"}) +      {:ok, activity2} = CommonAPI.post(user, %{"status" => "Hello 2"}) +      {:ok, activity3} = CommonAPI.post(user, %{"status" => "Hello 3"}) +      {:ok, activity4} = CommonAPI.post(user, %{"status" => "Hello 4"}) + +      notification1_id = +        Repo.get_by(SubscriptionNotification, activity_id: activity1.id).id |> to_string() + +      notification2_id = +        Repo.get_by(SubscriptionNotification, activity_id: activity2.id).id |> to_string() + +      notification3_id = +        Repo.get_by(SubscriptionNotification, activity_id: activity3.id).id |> to_string() + +      notification4_id = +        Repo.get_by(SubscriptionNotification, activity_id: activity4.id).id |> to_string() + +      conn = assign(conn, :user, subscriber) + +      # min_id +      conn_res = +        get(conn, "/api/v1/notifications/subscription?limit=2&min_id=#{notification1_id}") + +      result = json_response(conn_res, 200) +      assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result + +      # since_id +      conn_res = +        get(conn, "/api/v1/notifications/subscription?limit=2&since_id=#{notification1_id}") + +      result = json_response(conn_res, 200) +      assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result + +      # max_id +      conn_res = +        get(conn, "/api/v1/notifications/subscription?limit=2&max_id=#{notification4_id}") + +      result = json_response(conn_res, 200) +      assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result +    end + +    test "destroy multiple", %{conn: conn, user: user1, subscriber: user2} do +      # mutual subscription +      User.subscribe(user1, user2) + +      {:ok, activity1} = CommonAPI.post(user1, %{"status" => "Hello 1"}) +      {:ok, activity2} = CommonAPI.post(user1, %{"status" => "World 1"}) +      {:ok, activity3} = CommonAPI.post(user2, %{"status" => "Hello 2"}) +      {:ok, activity4} = CommonAPI.post(user2, %{"status" => "World 2"}) + +      notification1_id = +        Repo.get_by(SubscriptionNotification, activity_id: activity1.id).id |> to_string() + +      notification2_id = +        Repo.get_by(SubscriptionNotification, activity_id: activity2.id).id |> to_string() + +      notification3_id = +        Repo.get_by(SubscriptionNotification, activity_id: activity3.id).id |> to_string() + +      notification4_id = +        Repo.get_by(SubscriptionNotification, activity_id: activity4.id).id |> to_string() + +      conn = assign(conn, :user, user1) + +      conn_res = get(conn, "/api/v1/notifications/subscription") + +      result = json_response(conn_res, 200) + +      Enum.each(result, fn %{"id" => id} -> +        assert id in [notification3_id, notification4_id] +      end) + +      conn2 = assign(conn, :user, user2) + +      conn_res = get(conn2, "/api/v1/notifications/subscription") + +      result = json_response(conn_res, 200) + +      Enum.each(result, fn %{"id" => id} -> +        assert id in [notification1_id, notification2_id] +      end) + +      conn_destroy = +        delete(conn, "/api/v1/notifications/subscription/destroy_multiple", %{ +          "ids" => [notification3_id, notification4_id] +        }) + +      assert json_response(conn_destroy, 200) == %{} + +      conn_res = get(conn2, "/api/v1/notifications/subscription") + +      result = json_response(conn_res, 200) + +      Enum.each(result, fn %{"id" => id} -> +        assert id in [notification1_id, notification2_id] +      end) + +      assert length(Repo.all(SubscriptionNotification)) == 2 +    end +  end +    describe "reblogging" do      test "reblogs and returns the reblogged status", %{conn: conn} do        activity = insert(:note_activity) diff --git a/test/web/mastodon_api/mastodon_api_test.exs b/test/web/mastodon_api/mastodon_api_test.exs index 7fcb2bd55..848fce7ad 100644 --- a/test/web/mastodon_api/mastodon_api_test.exs +++ b/test/web/mastodon_api/mastodon_api_test.exs @@ -75,9 +75,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPITest do        User.subscribe(subscriber, user) -      {:ok, status} = CommonAPI.post(user, %{"status" => "Akariiiin"}) +      {:ok, status} = CommonAPI.post(user, %{"status" => "Akariiiin @#{subscriber.nickname}"}) -      {:ok, status1} = CommonAPI.post(user, %{"status" => "Magi"}) +      {:ok, status1} = CommonAPI.post(user, %{"status" => "Magi @#{subscriber.nickname}"})        {:ok, [notification]} = Notification.create_notifications(status)        {:ok, [notification1]} = Notification.create_notifications(status1)        res = MastodonAPI.get_notifications(subscriber) | 
