Merge develop
[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 @type t :: %__MODULE__{}
23
24 schema "notifications" do
25 field(:seen, :boolean, default: false)
26 belongs_to(:user, User, type: Pleroma.FlakeId)
27 belongs_to(:activity, Activity, type: Pleroma.FlakeId)
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) 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 info->'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 end
58
59 def for_user(user, opts \\ %{}) do
60 user
61 |> for_user_query()
62 |> Pagination.fetch_paginated(opts)
63 end
64
65 @doc """
66 Returns notifications for user received since given date.
67
68 ## Examples
69
70 iex> Pleroma.Notification.for_user_since(%Pleroma.User{}, ~N[2019-04-13 11:22:33])
71 [%Pleroma.Notification{}, %Pleroma.Notification{}]
72
73 iex> Pleroma.Notification.for_user_since(%Pleroma.User{}, ~N[2019-04-15 11:22:33])
74 []
75 """
76 @spec for_user_since(Pleroma.User.t(), NaiveDateTime.t()) :: [t()]
77 def for_user_since(user, date) do
78 from(n in for_user_query(user),
79 where: n.updated_at > ^date
80 )
81 |> Repo.all()
82 end
83
84 def set_read_up_to(%{id: user_id} = _user, id) do
85 query =
86 from(
87 n in Notification,
88 where: n.user_id == ^user_id,
89 where: n.id <= ^id,
90 update: [
91 set: [
92 seen: true,
93 updated_at: ^NaiveDateTime.utc_now()
94 ]
95 ]
96 )
97
98 Repo.update_all(query, [])
99 end
100
101 def read_one(%User{} = user, notification_id) do
102 with {:ok, %Notification{} = notification} <- get(user, notification_id) do
103 notification
104 |> changeset(%{seen: true})
105 |> Repo.update()
106 end
107 end
108
109 def get(%{id: user_id} = _user, id) do
110 query =
111 from(
112 n in Notification,
113 where: n.id == ^id,
114 join: activity in assoc(n, :activity),
115 preload: [activity: activity]
116 )
117
118 notification = Repo.one(query)
119
120 case notification do
121 %{user_id: ^user_id} ->
122 {:ok, notification}
123
124 _ ->
125 {:error, "Cannot get notification"}
126 end
127 end
128
129 def clear(user) do
130 from(n in Notification, where: n.user_id == ^user.id)
131 |> Repo.delete_all()
132 end
133
134 def destroy_multiple(%{id: user_id} = _user, ids) do
135 from(n in Notification,
136 where: n.id in ^ids,
137 where: n.user_id == ^user_id
138 )
139 |> Repo.delete_all()
140 end
141
142 def dismiss(%{id: user_id} = _user, id) do
143 notification = Repo.get(Notification, id)
144
145 case notification do
146 %{user_id: ^user_id} ->
147 Repo.delete(notification)
148
149 _ ->
150 {:error, "Cannot dismiss notification"}
151 end
152 end
153
154 def create_notifications(%Activity{data: %{"to" => _, "type" => "Create"}} = activity) do
155 object = Object.normalize(activity)
156
157 unless object && object.data["type"] == "Answer" do
158 users = get_notified_from_activity(activity)
159 notifications = Enum.map(users, fn user -> create_notification(activity, user) end)
160 {:ok, notifications}
161 else
162 {:ok, []}
163 end
164 end
165
166 def create_notifications(%Activity{data: %{"to" => _, "type" => type}} = activity)
167 when type in ["Like", "Announce", "Follow"] do
168 users = get_notified_from_activity(activity)
169 notifications = Enum.map(users, fn user -> create_notification(activity, user) end)
170 {:ok, notifications}
171 end
172
173 def create_notifications(_), do: {:ok, []}
174
175 # TODO move to sql, too.
176 def create_notification(%Activity{} = activity, %User{} = user) do
177 unless skip?(activity, user) do
178 notification = %Notification{user_id: user.id, activity: activity}
179 {:ok, notification} = Repo.insert(notification)
180 Streamer.stream("user", notification)
181 Streamer.stream("user:notification", notification)
182 Push.send(notification)
183 notification
184 end
185 end
186
187 def get_notified_from_activity(activity, local_only \\ true)
188
189 def get_notified_from_activity(
190 %Activity{data: %{"to" => _, "type" => type} = _data} = activity,
191 local_only
192 )
193 when type in ["Create", "Like", "Announce", "Follow"] do
194 recipients =
195 []
196 |> Utils.maybe_notify_to_recipients(activity)
197 |> Utils.maybe_notify_mentioned_recipients(activity)
198 |> Utils.maybe_notify_subscribers(activity)
199 |> Enum.uniq()
200
201 User.get_users_from_set(recipients, local_only)
202 end
203
204 def get_notified_from_activity(_, _local_only), do: []
205
206 def skip?(activity, user) do
207 [
208 :self,
209 :blocked,
210 :muted,
211 :followers,
212 :follows,
213 :non_followers,
214 :non_follows,
215 :recently_followed
216 ]
217 |> Enum.any?(&skip?(&1, activity, user))
218 end
219
220 def skip?(:self, activity, user) do
221 activity.data["actor"] == user.ap_id
222 end
223
224 def skip?(:blocked, activity, user) do
225 actor = activity.data["actor"]
226 User.blocks?(user, %{ap_id: actor})
227 end
228
229 def skip?(:muted, activity, user) do
230 actor = activity.data["actor"]
231
232 User.mutes?(user, %{ap_id: actor}) or CommonAPI.thread_muted?(user, activity)
233 end
234
235 def skip?(
236 :followers,
237 activity,
238 %{info: %{notification_settings: %{"followers" => false}}} = user
239 ) do
240 actor = activity.data["actor"]
241 follower = User.get_cached_by_ap_id(actor)
242 User.following?(follower, user)
243 end
244
245 def skip?(
246 :non_followers,
247 activity,
248 %{info: %{notification_settings: %{"non_followers" => false}}} = user
249 ) do
250 actor = activity.data["actor"]
251 follower = User.get_cached_by_ap_id(actor)
252 !User.following?(follower, user)
253 end
254
255 def skip?(:follows, activity, %{info: %{notification_settings: %{"follows" => false}}} = user) do
256 actor = activity.data["actor"]
257 followed = User.get_cached_by_ap_id(actor)
258 User.following?(user, followed)
259 end
260
261 def skip?(
262 :non_follows,
263 activity,
264 %{info: %{notification_settings: %{"non_follows" => false}}} = user
265 ) do
266 actor = activity.data["actor"]
267 followed = User.get_cached_by_ap_id(actor)
268 !User.following?(user, followed)
269 end
270
271 def skip?(:recently_followed, %{data: %{"type" => "Follow"}} = activity, user) do
272 actor = activity.data["actor"]
273
274 Notification.for_user(user)
275 |> Enum.any?(fn
276 %{activity: %{data: %{"type" => "Follow", "actor" => ^actor}}} -> true
277 _ -> false
278 end)
279 end
280
281 def skip?(_, _, _), do: false
282 end