[#1335] User: refactored :blocks field into :blocked_users relation.
[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.Utils
15 alias Pleroma.Web.Push
16 alias Pleroma.Web.Streamer
17
18 import Ecto.Query
19 import Ecto.Changeset
20 require Logger
21
22 @type t :: %__MODULE__{}
23
24 schema "notifications" do
25 field(:seen, :boolean, default: false)
26 belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
27 belongs_to(:activity, Activity, type: FlakeId.Ecto.CompatType)
28
29 timestamps()
30 end
31
32 def changeset(%Notification{} = notification, attrs) do
33 notification
34 |> cast(attrs, [:seen])
35 end
36
37 def for_user_query(user, opts \\ []) do
38 Notification
39 |> where(user_id: ^user.id)
40 |> where(
41 [n, a],
42 fragment(
43 "? not in (SELECT ap_id FROM users WHERE deactivated = 'true')",
44 a.actor
45 )
46 )
47 |> join(:inner, [n], activity in assoc(n, :activity))
48 |> join(:left, [n, a], object in Object,
49 on:
50 fragment(
51 "(?->>'id') = COALESCE((? -> 'object'::text) ->> 'id'::text)",
52 object.data,
53 a.data
54 )
55 )
56 |> preload([n, a, o], activity: {a, object: o})
57 |> exclude_muted(user, opts)
58 |> exclude_blocked(user)
59 |> exclude_visibility(opts)
60 end
61
62 defp exclude_blocked(query, user) do
63 blocked_ap_ids = User.blocked_ap_ids(user)
64
65 query
66 |> where([n, a], a.actor not in ^blocked_ap_ids)
67 |> where(
68 [n, a],
69 fragment("substring(? from '.*://([^/]*)')", a.actor) not in ^user.domain_blocks
70 )
71 end
72
73 defp exclude_muted(query, _, %{with_muted: true}) do
74 query
75 end
76
77 defp exclude_muted(query, user, _opts) do
78 query
79 |> where([n, a], a.actor not in ^user.muted_notifications)
80 |> join(:left, [n, a], tm in Pleroma.ThreadMute,
81 on: tm.user_id == ^user.id and tm.context == fragment("?->>'context'", a.data)
82 )
83 |> where([n, a, o, tm], is_nil(tm.user_id))
84 end
85
86 @valid_visibilities ~w[direct unlisted public private]
87
88 defp exclude_visibility(query, %{exclude_visibilities: visibility})
89 when is_list(visibility) do
90 if Enum.all?(visibility, &(&1 in @valid_visibilities)) do
91 query
92 |> where(
93 [n, a],
94 not fragment(
95 "activity_visibility(?, ?, ?) = ANY (?)",
96 a.actor,
97 a.recipients,
98 a.data,
99 ^visibility
100 )
101 )
102 else
103 Logger.error("Could not exclude visibility to #{visibility}")
104 query
105 end
106 end
107
108 defp exclude_visibility(query, %{exclude_visibilities: visibility})
109 when visibility in @valid_visibilities do
110 query
111 |> where(
112 [n, a],
113 not fragment(
114 "activity_visibility(?, ?, ?) = (?)",
115 a.actor,
116 a.recipients,
117 a.data,
118 ^visibility
119 )
120 )
121 end
122
123 defp exclude_visibility(query, %{exclude_visibilities: visibility})
124 when visibility not in @valid_visibilities do
125 Logger.error("Could not exclude visibility to #{visibility}")
126 query
127 end
128
129 defp exclude_visibility(query, _visibility), do: query
130
131 def for_user(user, opts \\ %{}) do
132 user
133 |> for_user_query(opts)
134 |> Pagination.fetch_paginated(opts)
135 end
136
137 @doc """
138 Returns notifications for user received since given date.
139
140 ## Examples
141
142 iex> Pleroma.Notification.for_user_since(%Pleroma.User{}, ~N[2019-04-13 11:22:33])
143 [%Pleroma.Notification{}, %Pleroma.Notification{}]
144
145 iex> Pleroma.Notification.for_user_since(%Pleroma.User{}, ~N[2019-04-15 11:22:33])
146 []
147 """
148 @spec for_user_since(Pleroma.User.t(), NaiveDateTime.t()) :: [t()]
149 def for_user_since(user, date) do
150 from(n in for_user_query(user),
151 where: n.updated_at > ^date
152 )
153 |> Repo.all()
154 end
155
156 def set_read_up_to(%{id: user_id} = _user, id) do
157 query =
158 from(
159 n in Notification,
160 where: n.user_id == ^user_id,
161 where: n.id <= ^id,
162 where: n.seen == false,
163 update: [
164 set: [
165 seen: true,
166 updated_at: ^NaiveDateTime.utc_now()
167 ]
168 ],
169 # Ideally we would preload object and activities here
170 # but Ecto does not support preloads in update_all
171 select: n.id
172 )
173
174 {_, notification_ids} = Repo.update_all(query, [])
175
176 Notification
177 |> where([n], n.id in ^notification_ids)
178 |> join(:inner, [n], activity in assoc(n, :activity))
179 |> join(:left, [n, a], object in Object,
180 on:
181 fragment(
182 "(?->>'id') = COALESCE((? -> 'object'::text) ->> 'id'::text)",
183 object.data,
184 a.data
185 )
186 )
187 |> preload([n, a, o], activity: {a, object: o})
188 |> Repo.all()
189 end
190
191 def read_one(%User{} = user, notification_id) do
192 with {:ok, %Notification{} = notification} <- get(user, notification_id) do
193 notification
194 |> changeset(%{seen: true})
195 |> Repo.update()
196 end
197 end
198
199 def get(%{id: user_id} = _user, id) do
200 query =
201 from(
202 n in Notification,
203 where: n.id == ^id,
204 join: activity in assoc(n, :activity),
205 preload: [activity: activity]
206 )
207
208 notification = Repo.one(query)
209
210 case notification do
211 %{user_id: ^user_id} ->
212 {:ok, notification}
213
214 _ ->
215 {:error, "Cannot get notification"}
216 end
217 end
218
219 def clear(user) do
220 from(n in Notification, where: n.user_id == ^user.id)
221 |> Repo.delete_all()
222 end
223
224 def destroy_multiple(%{id: user_id} = _user, ids) do
225 from(n in Notification,
226 where: n.id in ^ids,
227 where: n.user_id == ^user_id
228 )
229 |> Repo.delete_all()
230 end
231
232 def dismiss(%{id: user_id} = _user, id) do
233 notification = Repo.get(Notification, id)
234
235 case notification do
236 %{user_id: ^user_id} ->
237 Repo.delete(notification)
238
239 _ ->
240 {:error, "Cannot dismiss notification"}
241 end
242 end
243
244 def create_notifications(%Activity{data: %{"to" => _, "type" => "Create"}} = activity) do
245 object = Object.normalize(activity)
246
247 unless object && object.data["type"] == "Answer" do
248 users = get_notified_from_activity(activity)
249 notifications = Enum.map(users, fn user -> create_notification(activity, user) end)
250 {:ok, notifications}
251 else
252 {:ok, []}
253 end
254 end
255
256 def create_notifications(%Activity{data: %{"to" => _, "type" => type}} = activity)
257 when type in ["Like", "Announce", "Follow"] do
258 users = get_notified_from_activity(activity)
259 notifications = Enum.map(users, fn user -> create_notification(activity, user) end)
260 {:ok, notifications}
261 end
262
263 def create_notifications(_), do: {:ok, []}
264
265 # TODO move to sql, too.
266 def create_notification(%Activity{} = activity, %User{} = user) do
267 unless skip?(activity, user) do
268 notification = %Notification{user_id: user.id, activity: activity}
269 {:ok, notification} = Repo.insert(notification)
270
271 ["user", "user:notification"]
272 |> Streamer.stream(notification)
273
274 Push.send(notification)
275 notification
276 end
277 end
278
279 def get_notified_from_activity(activity, local_only \\ true)
280
281 def get_notified_from_activity(
282 %Activity{data: %{"to" => _, "type" => type} = _data} = activity,
283 local_only
284 )
285 when type in ["Create", "Like", "Announce", "Follow"] do
286 recipients =
287 []
288 |> Utils.maybe_notify_to_recipients(activity)
289 |> Utils.maybe_notify_mentioned_recipients(activity)
290 |> Utils.maybe_notify_subscribers(activity)
291 |> Enum.uniq()
292
293 User.get_users_from_set(recipients, local_only)
294 end
295
296 def get_notified_from_activity(_, _local_only), do: []
297
298 @spec skip?(Activity.t(), User.t()) :: boolean()
299 def skip?(activity, user) do
300 [
301 :self,
302 :followers,
303 :follows,
304 :non_followers,
305 :non_follows,
306 :recently_followed
307 ]
308 |> Enum.any?(&skip?(&1, activity, user))
309 end
310
311 @spec skip?(atom(), Activity.t(), User.t()) :: boolean()
312 def skip?(:self, activity, user) do
313 activity.data["actor"] == user.ap_id
314 end
315
316 def skip?(
317 :followers,
318 activity,
319 %{notification_settings: %{"followers" => false}} = user
320 ) do
321 actor = activity.data["actor"]
322 follower = User.get_cached_by_ap_id(actor)
323 User.following?(follower, user)
324 end
325
326 def skip?(
327 :non_followers,
328 activity,
329 %{notification_settings: %{"non_followers" => false}} = user
330 ) do
331 actor = activity.data["actor"]
332 follower = User.get_cached_by_ap_id(actor)
333 !User.following?(follower, user)
334 end
335
336 def skip?(:follows, activity, %{notification_settings: %{"follows" => false}} = user) do
337 actor = activity.data["actor"]
338 followed = User.get_cached_by_ap_id(actor)
339 User.following?(user, followed)
340 end
341
342 def skip?(
343 :non_follows,
344 activity,
345 %{notification_settings: %{"non_follows" => false}} = user
346 ) do
347 actor = activity.data["actor"]
348 followed = User.get_cached_by_ap_id(actor)
349 !User.following?(user, followed)
350 end
351
352 def skip?(:recently_followed, %{data: %{"type" => "Follow"}} = activity, user) do
353 actor = activity.data["actor"]
354
355 Notification.for_user(user)
356 |> Enum.any?(fn
357 %{activity: %{data: %{"type" => "Follow", "actor" => ^actor}}} -> true
358 _ -> false
359 end)
360 end
361
362 def skip?(_, _, _), do: false
363 end