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