Add basic mastodon notification support.
authorRoger Braun <roger@rogerbraun.net>
Mon, 11 Sep 2017 14:15:28 +0000 (16:15 +0200)
committerRoger Braun <roger@rogerbraun.net>
Mon, 11 Sep 2017 16:53:19 +0000 (18:53 +0200)
lib/pleroma/activity.ex
lib/pleroma/notification.ex [new file with mode: 0644]
lib/pleroma/user.ex
lib/pleroma/web/activity_pub/activity_pub.ex
lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
lib/pleroma/web/router.ex
priv/repo/migrations/20170911123607_create_notifications.exs [new file with mode: 0644]
test/notification_test.exs [new file with mode: 0644]

index f226c4c5f351d797a9eac90bc835645c230c10bd..9a5e6fc787c5035dcb5d5e5a7a1f5af058fbdfed 100644 (file)
@@ -1,11 +1,12 @@
 defmodule Pleroma.Activity do
   use Ecto.Schema
-  alias Pleroma.{Repo, Activity}
+  alias Pleroma.{Repo, Activity, Notification}
   import Ecto.Query
 
   schema "activities" do
     field :data, :map
     field :local, :boolean, default: true
+    has_many :notifications, Notification
 
     timestamps()
   end
diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex
new file mode 100644 (file)
index 0000000..f8835fc
--- /dev/null
@@ -0,0 +1,38 @@
+defmodule Pleroma.Notification do
+  use Ecto.Schema
+  alias Pleroma.{User, Activity, Notification, Repo}
+  import Ecto.Query
+
+  schema "notifications" do
+    field :seen, :boolean, default: false
+    belongs_to :user, Pleroma.User
+    belongs_to :activity, Pleroma.Activity
+
+    timestamps()
+  end
+
+  def for_user(user, opts \\ %{}) do
+    query = from n in Notification,
+      where: n.user_id == ^user.id,
+      order_by: [desc: n.id],
+      preload: [:activity],
+      limit: 20
+    Repo.all(query)
+  end
+
+  def create_notifications(%Activity{id: id, data: %{"to" => to, "type" => type}} = activity) when type in ["Create"] do
+    users = User.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
+    notification = %Notification{user_id: user.id, activity_id: activity.id}
+    {:ok, notification} = Repo.insert(notification)
+    notification
+  end
+end
+
index 4f5fcab5b549dd58cfdefec2e5a2b9723ec23d93..39d8cca768aeb7222b654de3d119fbf2ba409683 100644 (file)
@@ -2,7 +2,7 @@ defmodule Pleroma.User do
   use Ecto.Schema
 
   import Ecto.{Changeset, Query}
-  alias Pleroma.{Repo, User, Object, Web}
+  alias Pleroma.{Repo, User, Object, Web, Activity, Notification}
   alias Comeonin.Pbkdf2
   alias Pleroma.Web.{OStatus, Websub}
   alias Pleroma.Web.ActivityPub.ActivityPub
@@ -22,6 +22,7 @@ defmodule Pleroma.User do
     field :local, :boolean, default: true
     field :info, :map, default: %{}
     field :follower_address, :string
+    has_many :notifications, Notification
 
     timestamps()
   end
@@ -239,4 +240,12 @@ defmodule Pleroma.User do
 
     Repo.update(cs)
   end
+
+  def get_notified_from_activity(%Activity{data: %{"to" => to}} = activity) do
+    query = from u in User,
+      where: u.ap_id in ^to,
+      where: u.local == true
+
+    Repo.all(query)
+  end
 end
index 8ae32165824511f97e8c13ccf25572541689ef7b..e3dce9cba03a6da4d15193d94e286fc0ff200995 100644 (file)
@@ -1,5 +1,5 @@
 defmodule Pleroma.Web.ActivityPub.ActivityPub do
-  alias Pleroma.{Activity, Repo, Object, Upload, User, Web}
+  alias Pleroma.{Activity, Repo, Object, Upload, User, Web, Notification}
   alias Ecto.{Changeset, UUID}
   import Ecto.Query
   import Pleroma.Web.ActivityPub.Utils
@@ -9,7 +9,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     with nil <- Activity.get_by_ap_id(map["id"]),
          map <- lazy_put_activity_defaults(map),
          :ok <- insert_full_object(map) do
-      Repo.insert(%Activity{data: map, local: local})
+      {:ok, activity} = Repo.insert(%Activity{data: map, local: local})
+      Notification.create_notifications(activity)
+      {:ok, activity}
     else
       %Activity{} = activity -> {:ok, activity}
       error -> {:error, error}
index 16ee434c6443ae849422e1f9ca93602f66388610..07272e5b36aab8e163ce439567c77ce350445990 100644 (file)
@@ -1,6 +1,6 @@
 defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
   use Pleroma.Web, :controller
-  alias Pleroma.{Repo, Activity, User}
+  alias Pleroma.{Repo, Activity, User, Notification}
   alias Pleroma.Web.OAuth.App
   alias Pleroma.Web
   alias Pleroma.Web.MastodonAPI.{StatusView, AccountView}
@@ -132,6 +132,20 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
     end
   end
 
+  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"])
+      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})}
+        _ -> nil
+      end
+    end)
+    |> Enum.filter(&(&1))
+
+    json(conn, result)
+  end
+
   def empty_array(conn, _) do
     Logger.debug("Unimplemented, returning an empty array")
     json(conn, [])
index 9e725641dc045ced9475f96fc6caca881fc00e6e..161635558b6ddcec1b1040d62fec44903b95826c 100644 (file)
@@ -65,7 +65,7 @@ defmodule Pleroma.Web.Router do
     post "/statuses/:id/favourite", MastodonAPIController, :fav_status
     post "/statuses/:id/unfavourite", MastodonAPIController, :unfav_status
 
-    get "/notifications", MastodonAPIController, :empty_array
+    get "/notifications", MastodonAPIController, :notifications
   end
 
   scope "/api", Pleroma.Web do
diff --git a/priv/repo/migrations/20170911123607_create_notifications.exs b/priv/repo/migrations/20170911123607_create_notifications.exs
new file mode 100644 (file)
index 0000000..5be809f
--- /dev/null
@@ -0,0 +1,15 @@
+defmodule Pleroma.Repo.Migrations.CreateNotifications do
+  use Ecto.Migration
+
+  def change do
+    create table(:notifications) do
+      add :user_id, references(:users, on_delete: :delete_all)
+      add :activity_id, references(:activities, on_delete: :delete_all)
+      add :seen, :boolean, default: false
+
+      timestamps()
+    end
+
+    create index(:notifications, [:user_id])
+  end
+end
diff --git a/test/notification_test.exs b/test/notification_test.exs
new file mode 100644 (file)
index 0000000..f50b3cb
--- /dev/null
@@ -0,0 +1,23 @@
+defmodule Pleroma.NotificationTest do
+  use Pleroma.DataCase
+  alias Pleroma.Web.TwitterAPI.TwitterAPI
+  alias Pleroma.{User, Notification}
+  import Pleroma.Factory
+
+  describe "create_notifications" do
+    test "notifies someone when they are directly addressed" 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, [notification, other_notification]} = Notification.create_notifications(activity)
+
+      assert notification.user_id == other_user.id
+      assert notification.activity_id == activity.id
+      assert other_notification.user_id == third_user.id
+      assert other_notification.activity_id == activity.id
+    end
+  end
+end