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