Merge branch 'develop' into 'remove-avatar-header'
[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" => "Create"}} = activity) do
131 object = Object.normalize(activity)
132
133 unless object && object.data["type"] == "Answer" do
134 users = get_notified_from_activity(activity)
135 notifications = Enum.map(users, fn user -> create_notification(activity, user) end)
136 {:ok, notifications}
137 else
138 {:ok, []}
139 end
140 end
141
142 def create_notifications(%Activity{data: %{"to" => _, "type" => type}} = activity)
143 when type in ["Like", "Announce", "Follow"] do
144 users = get_notified_from_activity(activity)
145 notifications = Enum.map(users, fn user -> create_notification(activity, user) end)
146 {:ok, notifications}
147 end
148
149 def create_notifications(_), do: {:ok, []}
150
151 # TODO move to sql, too.
152 def create_notification(%Activity{} = activity, %User{} = user) do
153 unless skip?(activity, user) do
154 notification = %Notification{user_id: user.id, activity: activity}
155 {:ok, notification} = Repo.insert(notification)
156 Streamer.stream("user", notification)
157 Streamer.stream("user:notification", notification)
158 Push.send(notification)
159 notification
160 end
161 end
162
163 def get_notified_from_activity(activity, local_only \\ true)
164
165 def get_notified_from_activity(
166 %Activity{data: %{"to" => _, "type" => type} = _data} = activity,
167 local_only
168 )
169 when type in ["Create", "Like", "Announce", "Follow"] do
170 recipients =
171 []
172 |> Utils.maybe_notify_to_recipients(activity)
173 |> Utils.maybe_notify_mentioned_recipients(activity)
174 |> Utils.maybe_notify_subscribers(activity)
175 |> Enum.uniq()
176
177 User.get_users_from_set(recipients, local_only)
178 end
179
180 def get_notified_from_activity(_, _local_only), do: []
181
182 def skip?(activity, user) do
183 [
184 :self,
185 :blocked,
186 :muted,
187 :followers,
188 :follows,
189 :non_followers,
190 :non_follows,
191 :recently_followed
192 ]
193 |> Enum.any?(&skip?(&1, activity, user))
194 end
195
196 def skip?(:self, activity, user) do
197 activity.data["actor"] == user.ap_id
198 end
199
200 def skip?(:blocked, activity, user) do
201 actor = activity.data["actor"]
202 User.blocks?(user, %{ap_id: actor})
203 end
204
205 def skip?(:muted, activity, user) do
206 actor = activity.data["actor"]
207
208 User.mutes?(user, %{ap_id: actor}) or CommonAPI.thread_muted?(user, activity)
209 end
210
211 def skip?(
212 :followers,
213 activity,
214 %{info: %{notification_settings: %{"followers" => false}}} = user
215 ) do
216 actor = activity.data["actor"]
217 follower = User.get_cached_by_ap_id(actor)
218 User.following?(follower, user)
219 end
220
221 def skip?(
222 :non_followers,
223 activity,
224 %{info: %{notification_settings: %{"non_followers" => false}}} = user
225 ) do
226 actor = activity.data["actor"]
227 follower = User.get_cached_by_ap_id(actor)
228 !User.following?(follower, user)
229 end
230
231 def skip?(:follows, activity, %{info: %{notification_settings: %{"follows" => false}}} = user) do
232 actor = activity.data["actor"]
233 followed = User.get_cached_by_ap_id(actor)
234 User.following?(user, followed)
235 end
236
237 def skip?(
238 :non_follows,
239 activity,
240 %{info: %{notification_settings: %{"non_follows" => false}}} = user
241 ) do
242 actor = activity.data["actor"]
243 followed = User.get_cached_by_ap_id(actor)
244 !User.following?(user, followed)
245 end
246
247 def skip?(:recently_followed, %{data: %{"type" => "Follow"}} = activity, user) do
248 actor = activity.data["actor"]
249
250 Notification.for_user(user)
251 |> Enum.any?(fn
252 %{activity: %{data: %{"type" => "Follow", "actor" => ^actor}}} -> true
253 _ -> false
254 end)
255 end
256
257 def skip?(_, _, _), do: false
258 end