[#570] add user:notification stream
[akkoma] / lib / pleroma / notification.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Notification do
6 use Ecto.Schema
7
8 alias Pleroma.Activity
9 alias Pleroma.Notification
10 alias Pleroma.Object
11 alias Pleroma.Pagination
12 alias Pleroma.Repo
13 alias Pleroma.User
14 alias Pleroma.Web.CommonAPI
15 alias Pleroma.Web.CommonAPI.Utils
16 alias Pleroma.Web.Push
17 alias Pleroma.Web.Streamer
18
19 import Ecto.Query
20 import Ecto.Changeset
21
22 schema "notifications" do
23 field(:seen, :boolean, default: false)
24 belongs_to(:user, User, type: Pleroma.FlakeId)
25 belongs_to(:activity, Activity, type: Pleroma.FlakeId)
26
27 timestamps()
28 end
29
30 def changeset(%Notification{} = notification, attrs) do
31 notification
32 |> cast(attrs, [:seen])
33 end
34
35 def for_user_query(user) do
36 Notification
37 |> where(user_id: ^user.id)
38 |> where(
39 [n, a],
40 fragment(
41 "? not in (SELECT ap_id FROM users WHERE info->'deactivated' @> 'true')",
42 a.actor
43 )
44 )
45 |> join(:inner, [n], activity in assoc(n, :activity))
46 |> join(:left, [n, a], object in Object,
47 on:
48 fragment(
49 "(?->>'id') = COALESCE((? -> 'object'::text) ->> 'id'::text)",
50 object.data,
51 a.data
52 )
53 )
54 |> preload([n, a, o], activity: {a, object: o})
55 end
56
57 def for_user(user, opts \\ %{}) do
58 user
59 |> for_user_query()
60 |> Pagination.fetch_paginated(opts)
61 end
62
63 def set_read_up_to(%{id: user_id} = _user, id) do
64 query =
65 from(
66 n in Notification,
67 where: n.user_id == ^user_id,
68 where: n.id <= ^id,
69 update: [
70 set: [seen: true]
71 ]
72 )
73
74 Repo.update_all(query, [])
75 end
76
77 def read_one(%User{} = user, notification_id) do
78 with {:ok, %Notification{} = notification} <- get(user, notification_id) do
79 notification
80 |> changeset(%{seen: true})
81 |> Repo.update()
82 end
83 end
84
85 def get(%{id: user_id} = _user, id) do
86 query =
87 from(
88 n in Notification,
89 where: n.id == ^id,
90 join: activity in assoc(n, :activity),
91 preload: [activity: activity]
92 )
93
94 notification = Repo.one(query)
95
96 case notification do
97 %{user_id: ^user_id} ->
98 {:ok, notification}
99
100 _ ->
101 {:error, "Cannot get notification"}
102 end
103 end
104
105 def clear(user) do
106 from(n in Notification, where: n.user_id == ^user.id)
107 |> Repo.delete_all()
108 end
109
110 def destroy_multiple(%{id: user_id} = _user, ids) do
111 from(n in Notification,
112 where: n.id in ^ids,
113 where: n.user_id == ^user_id
114 )
115 |> Repo.delete_all()
116 end
117
118 def dismiss(%{id: user_id} = _user, id) do
119 notification = Repo.get(Notification, id)
120
121 case notification do
122 %{user_id: ^user_id} ->
123 Repo.delete(notification)
124
125 _ ->
126 {:error, "Cannot dismiss notification"}
127 end
128 end
129
130 def create_notifications(%Activity{data: %{"to" => _, "type" => type}} = activity)
131 when type in ["Create", "Like", "Announce", "Follow"] do
132 object = Object.normalize(activity)
133
134 unless object && object.data["type"] == "Answer" do
135 users = get_notified_from_activity(activity)
136 notifications = Enum.map(users, fn user -> create_notification(activity, user) end)
137 {:ok, notifications}
138 else
139 {:ok, []}
140 end
141 end
142
143 def create_notifications(_), do: {:ok, []}
144
145 # TODO move to sql, too.
146 def create_notification(%Activity{} = activity, %User{} = user) do
147 unless skip?(activity, user) do
148 notification = %Notification{user_id: user.id, activity: activity}
149 {:ok, notification} = Repo.insert(notification)
150 Streamer.stream("user", notification)
151 Streamer.stream("user:notification", notification)
152 Push.send(notification)
153 notification
154 end
155 end
156
157 def get_notified_from_activity(activity, local_only \\ true)
158
159 def get_notified_from_activity(
160 %Activity{data: %{"to" => _, "type" => type} = _data} = activity,
161 local_only
162 )
163 when type in ["Create", "Like", "Announce", "Follow"] do
164 recipients =
165 []
166 |> Utils.maybe_notify_to_recipients(activity)
167 |> Utils.maybe_notify_mentioned_recipients(activity)
168 |> Utils.maybe_notify_subscribers(activity)
169 |> Enum.uniq()
170
171 User.get_users_from_set(recipients, local_only)
172 end
173
174 def get_notified_from_activity(_, _local_only), do: []
175
176 def skip?(activity, user) do
177 [
178 :self,
179 :blocked,
180 :muted,
181 :followers,
182 :follows,
183 :non_followers,
184 :non_follows,
185 :recently_followed
186 ]
187 |> Enum.any?(&skip?(&1, activity, user))
188 end
189
190 def skip?(:self, activity, user) do
191 activity.data["actor"] == user.ap_id
192 end
193
194 def skip?(:blocked, activity, user) do
195 actor = activity.data["actor"]
196 User.blocks?(user, %{ap_id: actor})
197 end
198
199 def skip?(:muted, activity, user) do
200 actor = activity.data["actor"]
201
202 User.mutes?(user, %{ap_id: actor}) or CommonAPI.thread_muted?(user, activity)
203 end
204
205 def skip?(
206 :followers,
207 activity,
208 %{info: %{notification_settings: %{"followers" => false}}} = user
209 ) do
210 actor = activity.data["actor"]
211 follower = User.get_cached_by_ap_id(actor)
212 User.following?(follower, user)
213 end
214
215 def skip?(
216 :non_followers,
217 activity,
218 %{info: %{notification_settings: %{"non_followers" => false}}} = user
219 ) do
220 actor = activity.data["actor"]
221 follower = User.get_cached_by_ap_id(actor)
222 !User.following?(follower, user)
223 end
224
225 def skip?(:follows, activity, %{info: %{notification_settings: %{"follows" => false}}} = user) do
226 actor = activity.data["actor"]
227 followed = User.get_cached_by_ap_id(actor)
228 User.following?(user, followed)
229 end
230
231 def skip?(
232 :non_follows,
233 activity,
234 %{info: %{notification_settings: %{"non_follows" => false}}} = user
235 ) do
236 actor = activity.data["actor"]
237 followed = User.get_cached_by_ap_id(actor)
238 !User.following?(user, followed)
239 end
240
241 def skip?(:recently_followed, %{data: %{"type" => "Follow"}} = activity, user) do
242 actor = activity.data["actor"]
243
244 Notification.for_user(user)
245 |> Enum.any?(fn
246 %{activity: %{data: %{"type" => "Follow", "actor" => ^actor}}} -> true
247 _ -> false
248 end)
249 end
250
251 def skip?(_, _, _), do: false
252 end