From 61adf676d56db274cb4688a137787e8806e77be9 Mon Sep 17 00:00:00 2001 From: Roger Braun Date: Mon, 11 Sep 2017 16:15:28 +0200 Subject: [PATCH] Add basic mastodon notification support. --- lib/pleroma/activity.ex | 3 +- lib/pleroma/notification.ex | 38 +++++++++++++++++++ lib/pleroma/user.ex | 11 +++++- lib/pleroma/web/activity_pub/activity_pub.ex | 6 ++- .../mastodon_api/mastodon_api_controller.ex | 16 +++++++- lib/pleroma/web/router.ex | 2 +- .../20170911123607_create_notifications.exs | 15 ++++++++ test/notification_test.exs | 23 +++++++++++ 8 files changed, 108 insertions(+), 6 deletions(-) create mode 100644 lib/pleroma/notification.ex create mode 100644 priv/repo/migrations/20170911123607_create_notifications.exs create mode 100644 test/notification_test.exs diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index f226c4c5f..9a5e6fc78 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -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 index 000000000..f8835fce6 --- /dev/null +++ b/lib/pleroma/notification.ex @@ -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 + diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 4f5fcab5b..39d8cca76 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -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 @@ def update_follower_count(%User{} = 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 diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 8ae321658..e3dce9cba 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -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 @@ def insert(map, local \\ true) when is_map(map) 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} diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index 16ee434c6..07272e5b3 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -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 @@ def unfav_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) 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, []) diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 9e725641d..161635558 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -65,7 +65,7 @@ def user_fetcher(username) 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 index 000000000..5be809fb8 --- /dev/null +++ b/priv/repo/migrations/20170911123607_create_notifications.exs @@ -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 index 000000000..f50b3cb24 --- /dev/null +++ b/test/notification_test.exs @@ -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